Thursday 31 December 2015

What are Normal permissions in Android?

There are two types of permissions:-

1. Normal permissions 
2. Runtime Permissions

We are familiar with Runtime permissions, here we will be discussing about Normal Permissions.
Normal permissions are the permissions which are automatically granted during install time. We need not to check these in our code(i.e runtime).And these are the permissions which user can not revoke from app settings as well.
We simply declare these permissions in the Android Manifest.xml and it will work nomally as did earlier.
.
Below is the list of permissions :-

android.permission.WRITE_SYNC_SETTINGS
com.android.alarm.permission.SET_ALARM
com.android.launcher.permission.INSTALL_SHORTCUT
com.android.launcher.permission.UNINSTALL_SHORTCUT
android.permission.ACCESS_WIMAX_STATE
android.permission.BLUETOOTH
android.permission.BLUETOOTH_ADMIN
android.permission.BROADCAST_STICKY
android.permission.CHANGE_NETWORK_STATE
android.permission.CHANGE_WIFI_MULTICAST_STATE
android.permission.CHANGE_WIFI_STATE
android.permission.CHANGE_WIMAX_STATE
android.permission.DISABLE_KEYGUARD
android.permission.EXPAND_STATUS_BAR
android.permission.ACCESS_LOCATION_EXTRA_COMMANDS
android.permission.ACCESS_NETWORK_STATE
android.permission.ACCESS_NOTIFICATION_POLICY
android.permission.ACCESS_WIFI_STATE
android.permission.FLASHLIGHT
android.permission.GET_ACCOUNTS
android.permission.GET_PACKAGE_SIZE
android.permission.INTERNET
android.permission.READ_SYNC_STATS
android.permission.RECEIVE_BOOT_COMPLETED
android.permission.REORDER_TASKS
android.permission.REQUEST_INSTALL_PACKAGES
android.permission.KILL_BACKGROUND_PROCESSES
android.permission.MODIFY_AUDIO_SETTINGS
android.permission.NFC
android.permission.SET_WALLPAPER_HINTS
android.permission.SUBSCRIBED_FEEDS_READ
android.permission.TRANSMIT_IR
android.permission.USE_FINGERPRINT
android.permission.VIBRATE
android.permission.WAKE_LOCK
android.permission.READ_SYNC_SETTINGS
android.permission.SET_TIME_ZONE
android.permission.SET_WALLPAPER

Sunday 6 December 2015

Download/Upload files using Dropbox Api Android

This tutorial is about Downloading/Uploading files from/to Dropbox using DropboxApi Android.


Firstly create your App key and App Secret key to use this api from below link.
Dropbox App Console : https://www.dropbox.com/developers-v1/apps

Include the below jars files from the downloaded Dropbox Sdk :
1. dropbox-android-sdk-1.6.3.jar
2. json_simple-1.1.jar

1. MainActivity.java

public class MainActivity extends AppCompatActivity {

        //path of the file to be uploaded here    
String path= Environment.getExternalStorageDirectory()
            .getAbsolutePath() + "/photo.jpg";
    final static private String APP_KEY = "";
    final static private String APP_SECRET = "";
    private DropboxAPI<AndroidAuthSession> mDBApi;
    DropboxAPI.Entry response;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        /**
         *   i am using this mode . you can use the Async task to upload and download file
         */
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
        StrictMode.setThreadPolicy(policy);
        setContentView(R.layout.activity_main);
        //Generate dropbox session from the generated keys
        AppKeyPair appKeys = new AppKeyPair(APP_KEY, APP_SECRET);
        AndroidAuthSession session = new AndroidAuthSession(appKeys);
        mDBApi = new DropboxAPI<AndroidAuthSession>(session);
        //Check if user has already authorized the dropbox app for you..
        if(!getAccessToken().equalsIgnoreCase("")){
            mDBApi.getSession().setOAuth2AccessToken(getAccessToken());
        }else{
            //if not authorized. i.e for the first time then
            mDBApi = new DropboxAPI<AndroidAuthSession>(session);
            mDBApi.getSession().startOAuth2Authentication(MainActivity.this);
        }

        ((Button) findViewById(R.id.uploaddata)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                uploadFile(path);
            }
        });

        ((Button) findViewById(R.id.downloadfile)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                downloadFile(Environment.getExternalStorageDirectory()
                        .getAbsolutePath());
            }
        });
    }

    protected void onResume() {
        super.onResume();
        //if first time and save the Access Token here
        if(getAccessToken().equalsIgnoreCase("")) {
            if (mDBApi.getSession().authenticationSuccessful()) {
                try {
                    // Required to complete auth, sets the access token on the session
                    mDBApi.getSession().finishAuthentication();
                    String accessToken = mDBApi.getSession().getOAuth2AccessToken();
                    saveAccessToken(accessToken);
                } catch (IllegalStateException e) {
                    Log.i("Error", "Error authenticating", e);
                }
            }
        }
    }

    private void saveAccessToken(String accessToken){
        SharedPreferences sharedPreferences=getSharedPreferences("Prefs", Context.MODE_PRIVATE);
        sharedPreferences.edit().putString("accessToken", accessToken).commit();
    }

    private String getAccessToken(){
        SharedPreferences sharedPreferences=getSharedPreferences("Prefs", Context.MODE_PRIVATE);
        return sharedPreferences.getString("accessToken","");
    }

    public void uploadFile(String file_path) {
        File file = new File(file_path);
        FileInputStream inputStream = null;
        try {
            inputStream = new FileInputStream(file);
        } catch (FileNotFoundException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        try {
            response = mDBApi.putFile("/photo.jpg", inputStream,
                    file.length(), null, null);
            if(response.rev!=null){
                Toast.makeText(this,"File Uploaded successfully..",Toast.LENGTH_LONG).show();
            }
            Log.i("Success", "The uploaded file's rev is: " + response.rev);
        } catch (DropboxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    public void downloadFile(String file_path) {
       //Path where you want to stored the downloaded file form dropbox
        File file = new File(file_path+"/testphoto.jpg");
        FileOutputStream outputStream = null;
        try {
            outputStream = new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        DropboxAPI.DropboxFileInfo info = null;
        try {
            info = mDBApi.getFile("/photo.jpg", null, outputStream, null);
            if(info.getMetadata().rev!=null){
                Toast.makeText(this,"File Downloaded successfully..",Toast.LENGTH_LONG).show();
            }
            Log.i("Success", "The file's rev is: "
                    + info.getMetadata().rev);
        } catch (DropboxException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

2. Manifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kamal.dropboxdemoapi" >
    <uses-permission android:name="android.permission.INTERNET"></uses-permission>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"></uses-permission>
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >

        <activity
            android:name="com.dropbox.client2.android.AuthActivity"
            android:launchMode="singleTask"
            android:configChanges="orientation|keyboard">
            <intent-filter>
                <!-- Change this to be db- followed by your app key -->
                <data android:scheme="db-Your App key here" />
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.BROWSABLE"/>
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>


Find the source from  Github : https://github.com/CammyKamal/BlogTutorials/tree/master/DropBoxDemoApi

Friday 4 December 2015

Handling GCM Push Notifications in Android Flavors

In Android Flavors generally we have different package names in our single app for flavours.

When implementing GCM Push Notifications in our android app in case of Flavors there is very little thing that we need to do differently.

Since our Package Name i.e application id is going to be different for different Flavors then push notifications going to be a problem in that case.

Below is the permissions that we generally declare in manifest file when using GCM.
   

 <!-- GCM requires a Google account. -->
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />

    <!-- Creates a custom permission so only this app can receive its messages. -->
    <permission
        android:name="yourpackagename.permission.C2D_MESSAGE"
        android:protectionLevel="signature" />

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

    <!-- This app has permission to register and receive data message. -->
    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
   
   
    Now suppose if we are implementing flavours then way of defining these permissions will change.
    Suppose we have below flavours in our build.gradle.
   
    productFlavors {
                        paid
                    {
                        applicationId "com.example.paid"
                        versionCode 1
                        versionName "1.0"
                    }
                        free
                    {
                        applicationId "com.example.free"
                        versionCode 1
                        versionName "1.0"
                    }
                }
               
           
Now when running GCM according to different variants will be a problem. GCM will work accordingly whose permission with package name is defined in manifest.

So add runtime Package Name to the manifest permissions in case of Android Flavors. Below is how can we declare single permissions for multiple Android Flavors in android manifest file.
   
   

    <!-- GCM notifications permissions-->


    <uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />

    

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

    

    <permission android:name="${applicationId}.permission.C2D_MESSAGE"

        android:protectionLevel="signature" />

    

    <uses-permission android:name="${applicationId}.permission.C2D_MESSAGE" />

    
   
Here ${applicationId} will be replaced by the applicationId of the running build variant defined in build.gradle of the app.


Cheers!!

Thursday 3 December 2015

Snackbar to show Internet Connectivity Changes in App


Here Snackbar will be shown everytime when internet connection is available or connection lost for user via broadcast receiver.

Snackbar works from api 8 and above so its a cool thing to implement for showing such short messages.

 You need to extend your activity with AppCompatActivity for Snackbar.

Add below dependency in your build.gradle compile 'com.android.support:design:22.2.1'

 1. MainActivity.java

public class MainActivity extends AppCompatActivity {
    public static int TYPE_WIFI = 1;
    public static int TYPE_MOBILE = 2;
    public static int TYPE_NOT_CONNECTED = 0;
    private Snackbar snackbar;
    private CoordinatorLayout coordinatorLayout;
    private boolean internetConnected=true;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        coordinatorLayout=(CoordinatorLayout)findViewById(R.id.coordinatorlayout);
    }

    @Override
    protected void onResume() {
        super.onResume();
        registerInternetCheckReceiver();
    }

    @Override
    protected void onPause() {
        super.onPause();
        unregisterReceiver(broadcastReceiver);
    }
    /**
     *  Method to register runtime broadcast receiver to show snackbar alert for internet connection..
     */
    private void registerInternetCheckReceiver() {
        IntentFilter internetFilter = new IntentFilter();
        internetFilter.addAction("android.net.wifi.STATE_CHANGE");
        internetFilter.addAction("android.net.conn.CONNECTIVITY_CHANGE");
        registerReceiver(broadcastReceiver, internetFilter);
    }

    /**
     *  Runtime Broadcast receiver inner class to capture internet connectivity events
     */
    public BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            String status = getConnectivityStatusString(context);
            setSnackbarMessage(status,false);
        }
    };

    public static int getConnectivityStatus(Context context) {
        ConnectivityManager cm = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetwork = cm.getActiveNetworkInfo();
        if (null != activeNetwork) {
            if(activeNetwork.getType() == ConnectivityManager.TYPE_WIFI)
                return TYPE_WIFI;

            if(activeNetwork.getType() == ConnectivityManager.TYPE_MOBILE)
                return TYPE_MOBILE;
        }
        return TYPE_NOT_CONNECTED;
    }

    public static String getConnectivityStatusString(Context context) {
        int conn = getConnectivityStatus(context);
        String status = null;
        if (conn == TYPE_WIFI) {
            status = "Wifi enabled";
        } else if (conn == TYPE_MOBILE) {
            status = "Mobile data enabled";
        } else if (conn == TYPE_NOT_CONNECTED) {
            status = "Not connected to Internet";
        }
        return status;
    }
    private void setSnackbarMessage(String status,boolean showBar) {
        String internetStatus="";
        if(status.equalsIgnoreCase("Wifi enabled")||status.equalsIgnoreCase("Mobile data enabled")){
            internetStatus="Internet Connected";
        }else {
            internetStatus="Lost Internet Connection";
        }
        snackbar = Snackbar
                .make(coordinatorLayout, internetStatus, Snackbar.LENGTH_LONG)
                .setAction("X", new View.OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        snackbar.dismiss();
                    }
                });
        // Changing message text color
        snackbar.setActionTextColor(Color.WHITE);
        // Changing action button text color
        View sbView = snackbar.getView();
        TextView textView = (TextView) sbView.findViewById(android.support.design.R.id.snackbar_text);
        textView.setTextColor(Color.WHITE);
        if(internetStatus.equalsIgnoreCase("Lost Internet Connection")){
            if(internetConnected){
                snackbar.show();
                internetConnected=false;
            }
        }else{
            if(!internetConnected){
                internetConnected=true;
                snackbar.show();
            }
        }
    }
}


2. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/coordinatorlayout"
    android:fitsSystemWindows="true"
    tools:context="com.example.kamalvaid.internetcheckandsnackbar.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <android.support.design.widget.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom|end"
        android:layout_margin="@dimen/fab_margin"
        android:src="@android:drawable/ic_dialog_email" />

</android.support.design.widget.CoordinatorLayout>
 
 
3. Add these permissions to manifest file

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


Github Link https://github.com/CammyKamal/BlogTutorials/tree/master/InternetCheckAndSnackBar

Output:


Monday 23 November 2015

Runtime Permissions in 6.0 Android MarshMallow

Recently i was studying about 6.0 runtime permissions and found it very interesting from both developer point of view as well as user.

On the basis of that i've developed a small demo which i am sharing and will help us understanding 6.0 runtime permissions in a better way.

So earlier we use to define permissions in the manifest and then simply use that feature in code. It is same for pre-marshmallow Devices but for Marshmallow and above we need to check for permissions first i.e if their are granted by user or not in order to perform certain functionalities.

 For example if i want to perform a call feature in 6.0 then i need to ask for permission from user before using it. So if user allows/grant that permission then only i'll be able to perform that functionality ahead, else i can't. So lets look into code how this works :

 1. MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        prepareViews();
    }

    private void prepareViews() {
        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Snackbar.make(view, "Replace with your own action", Snackbar.LENGTH_LONG)
                        .setAction("Action", null).show();
            }
        });

        ((Button)findViewById(R.id.permission_btn)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // here check whether app is runnung on 23 or greater then check for permission else perform normal functionality
                if (Build.VERSION.SDK_INT >= 23) {
                    checkPermission();
                } else {
                    Toast.makeText(MainActivity.this, "Device is below 6.0 do your work", Toast.LENGTH_LONG).show();
                }
            }
        });
        ((Button)findViewById(R.id.write_permission_btn)).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                if (Build.VERSION.SDK_INT >= 23) {
                    checkCallPermission();
                } else {
                    Toast.makeText(MainActivity.this, "Device is below 6.0 do your work", Toast.LENGTH_LONG).show();
                }
            }
        });

    }


    private void checkPermission(){
        if (ContextCompat.checkSelfPermission(this,Manifest.permission.ACCESS_COARSE_LOCATION)
                != PackageManager.PERMISSION_GRANTED) {
            // This condition checks whether user has earlier denied the permission or not just by clicking on deny in the permission dialog.
            //Remember not on the never ask check box then deny in the permission dialog
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.ACCESS_COARSE_LOCATION)) {
                /**
                 * Show an explanation to user why this permission in needed by us. Here using alert dialog to show the permission use.
                 */
                new AlertDialog.Builder(this)
                        .setTitle("Permission Required")
                        .setMessage("This permission was denied earlier by you. This permission is required to access your location for app accuracy purposes.So, in order to use this feature please allow this permission by clickking ok.")
                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},
                                        1);
                            }
                        })
                        .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .show();

            } else {
                // Just ask for the permission for first time. This block will come into play when user is trying to use feature which requires permission grant.
                //So for the first time user will be into this else block. So just ask for the permission you need by showing default permission dialog
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.ACCESS_COARSE_LOCATION},1);
            }
        }else {
            // If permission is already granted by user then we will be into this else block. So do whatever is required here
            Toast.makeText(this,"Permission Aleardy granted",Toast.LENGTH_LONG).show();
        }
    }
    private void checkCallPermission(){
        if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)
                != PackageManager.PERMISSION_GRANTED) {
            if (ActivityCompat.shouldShowRequestPermissionRationale(this,
                    Manifest.permission.CALL_PHONE)) {
                /**
                 *
                 */
                new AlertDialog.Builder(this)
                        .setTitle("Permission Required")
                        .setMessage("This permission was denied earlier by you. This permission is required to call from app .So, in order to use this feature please allow this permission by clicking ok.")
                        .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                                ActivityCompat.requestPermissions(MainActivity.this,
                                        new String[]{Manifest.permission.CALL_PHONE},
                                        2);
                            }
                        })
                        .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener() {
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .setIcon(android.R.drawable.ic_dialog_alert)
                        .show();

            } else {
                ActivityCompat.requestPermissions(this,
                        new String[]{Manifest.permission.CALL_PHONE},2);
            }
        }else {
            Toast.makeText(this,"Permission Aleardy granted",Toast.LENGTH_LONG).show();
        }
    }

    /**
     *  This method will be invoked when user allows or deny's a permission from the permission dialog so take actions accordingly.
     * @param requestCode
     * @param permissions
     * @param grantResults
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        switch (requestCode) {
            case 1:
                // If request is cancelled, the result arrays are empty.
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    // permission was granted, Do the Location -related task you need to do.
                    Toast.makeText(this, "COARSE Permission granted", Toast.LENGTH_LONG).show();
                } else {
                    // This block will be called when user has denied the permission. There will be two possibilities.
                    //1. User has simple denied the permission by clicking on the deny button the permission dialog.
                    //2. User has ticked the never show dialog and denied the permission from the permission dialog.
                    String permission = permissions[0];
                    boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
                    if (!showRationale) {
                        //We will be in this block when User has ticked the never show dialog and denied the permission from the permission dialog
                        //Here we can not request the permission again if user has denied the permission with never ask option enabled.
                        //Only way is to show a imfo dialog and ask user to grant the permission from settings.
                        Toast.makeText(this, "COARSE Permission Denied with never show options", Toast.LENGTH_LONG).show();
                    } else {
                        //We will be in this block when User has denied the permission from the permission dialog and not clicked on the never show again checkbox.
                        //We can show a explaination why we need this permission and request permission again.
                        Toast.makeText(this, "COARSE Permission Denied", Toast.LENGTH_LONG).show();
                        //permission denied,Disable the functionality that depends on this permission.
                    }

                }
                break;
            case 2:
                if (grantResults.length > 0
                        && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                    Toast.makeText(this, "Call Permission granted", Toast.LENGTH_LONG).show();
                } else {
                    String permission = permissions[0];
                    boolean showRationale = ActivityCompat.shouldShowRequestPermissionRationale(this, permission);
                    if (!showRationale) {
                        Toast.makeText(this, "Call Permission Denied with never show options", Toast.LENGTH_LONG).show();
                    } else {
                        Toast.makeText(this, "Call Permission Denied", Toast.LENGTH_LONG).show();
                    }
                    break;
                }
        }
    }
}

2. activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
 >

    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Check Permission for Location"
        android:id="@+id/permission_btn"
        />

    <Button
        android:layout_width="match_parent"
        android:layout_below="@+id/permission_btn"
        android:layout_height="wrap_content"
        android:text="call phone Check Permission for Location"
        android:id="@+id/write_permission_btn"
        />
</RelativeLayout> 

3. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.kamalvaid.marshmallowfeatures" >


    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
    <uses-permission android:name="android.permission.CALL_PHONE" />
    
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:theme="@style/AppTheme.NoActionBar" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

Github Link :- https://github.com/CammyKamal/BlogTutorials/tree/master/MarshMallowFeatures

Happy coding!!

 References : http://developer.android.com/index.html

Tuesday 17 November 2015

Loading images in a GridView or Listview from drawables at Runtime using Imageloader



You can use this code where you want to get the dynamic resource id (i.e in int). I Mostly used in Adapters (for gridview, listview).

ImageArray for dummy purpose

String[] imagesArray={"image1","image2","image3","image4","image5","image6"};
   

First Declare your imageloader

ImageLoader mImageloader;


Define your DisplayOptions as per requirement

mImageOptions = new DisplayImageOptions.Builder()
        .showImageOnLoading(R.drawable.ic_launcher)
        .showImageForEmptyUri(R.drawable.ic_launcher)
        .showImageOnFail(R.drawable.ic_launcher)
        .cacheInMemory(true)
        .cacheOnDisk(true)
        .considerExifParams(true)
        .bitmapConfig(Bitmap.Config.RGB_565)
        .build();


Define config as per requirement   

   
 ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(mContext)
        .threadPriority(Thread.NORM_PRIORITY - 2)
        .denyCacheImageMultipleSizesInMemory()
        .diskCacheFileNameGenerator(new Md5FileNameGenerator())
        .diskCacheSize(50 * 1024 * 1024) // 50 Mb
        .tasksProcessingOrder(QueueProcessingType.LIFO)
        .writeDebugLogs() // Remove for release app
        .build();
        // Initialize ImageLoader with configuration.
        ImageLoader.getInstance().init(config);
        mImageloader=ImageLoader.getInstance();

        
Then Load your image into your imageview
mImageloader
        .displayImage(("drawable://" + getResourceId(imagesArray[index],"drawable","com.example.dummy", mContext)), YourImageView, mImageOptions, new SimpleImageLoadingListener() {
            @Override
            public void onLoadingStarted(String imageUri, View view) {
            }

            @Override
            public void onLoadingFailed(String imageUri, View view, FailReason failReason) {
            }

            @Override
            public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
            }
        }, new ImageLoadingProgressListener() {
            @Override
            public void onProgressUpdate(String imageUri, View view, int current, int total) {
            }
        });
        
        

Use this method to get the resource id
public  int getResourceId(String fileName, String resourceName, String packageName,Context mContext) 
    {
        try {
            return mContext.getResources().getIdentifier(fileName, resourceName, packageName);
        } catch (Exception e) {
            e.printStackTrace();
            return -1;
        } 
    }       

                           

Sunday 11 October 2015

What is 65K Method limit in Android? How 65k Method limit handled by different build Systems of Android.

65k Method limit, well i recently faced this problem in one my android application. Trust me this is one of the worst problem you want to get into while development of your application.

So what is 65k method limit in Android? Firstly we should know this then only we can solve this.

65k Method Limit:

Apk i.e  Android Application contains DEX (dalvik executables i.e executable bytecode) as we know. So app contains a Single DEX i.e One Dex per app.

Single DEX file refers to 65,536 methods(including Android framework methods, library methods, and methods in your own code). So keep this thing in mind if your application is going to be big.

That's how error looks in Console =>
Earlier build versions :

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0, 0xffff]: 65536

Recent build versions :

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.

So first thing we never want to get into this stitution. And if we're into this stitution then how can we get rid of this is as follows:

We need to generate more then One Dex file for our app which is known as a multidex configuration. In order to achieve this we need to configure our Build app.

This problem mostly occurs on version prior to 5.0. Beacuse earlier versions uses Dalvik runtime for executing code, where as 5+ uses ART(Android runtime).

So how these android runtime works when this 65k problem occurs.ART is present in 5+ versions which supports loading multi dex files from an apk. So we need to configure our build system for earlier version in order to implement multi dex concept for handling 65k error.
Solution to this problem is "Multidex Support Library".

We need to modify our gradle file firstly as follows :

android {
    compileSdkVersion 21
    buildToolsVersion "21.1.0"
    defaultConfig {
        minSdkVersion 14
        targetSdkVersion 21

    // Enabling multidex support.
        multiDexEnabled true
 
}

}


dependencies {
  compile 'com.android.support:multidex:1.0.0'
}


In your manifest add the MultiDexApplication class from the multidex support library .

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.android.multidex.myapplication">
    <application
        android:name="android.support.multidex.MultiDexApplication">
    </application>
</manifest>

So when These things are done Android build Tools will generate :

1.  Primary dex (classes.dex)
2.  Supporting (classes2.dex, classes3.dex) as needed.

The build system will then package them into an APK file for distribution.

So this how you can overcome 65k method problem. But best practice would be reducing code. you can try following things that i would recommend :

1. Remove large libs if included in your project. Beacuse you will be using few methods for your purpose rather then whole lib. So try avoid such things if you're facing such an issue.

2.Remove unused code via proguard.


References : https://developer.android.com/


Happy coding!!

Saturday 10 October 2015

Toolbar Widget as Action Bar

Hi guys, Recently in my android projects i used toolbar alot. And most common use i found was showing a title with a back button at the top in the toolbar. Well i used both in Fragments and Activities. So how we can create a simple toolbar is as follows :



1. MainActivity
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    private Toolbar toolbar;
    private TextView tvTitle;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setToolBar();
    }

    private void setToolBar() {
        toolbar=(Toolbar)findViewById(R.id.toolbar);
        tvTitle=(TextView)findViewById(R.id.tv_title);
        tvTitle.setText("Toolbar Title");
        setSupportActionBar(toolbar);
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        switch (item.getItemId()){
            case android.R.id.home:
                finish();
                break;
        }
        return super.onOptionsItemSelected(item);
    }
}
2. activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <include
        layout="@layout/toolbar"/>

    <TextView android:text="@string/hello_world"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true" />

</RelativeLayout>
3. toolbar.xml


<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">
    <android.support.v7.widget.Toolbar
    android:id="@+id/toolbar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
 android:minHeight="?attr/actionBarSize"
 app:theme="@style/ToolBarTheme">


        <TextView
            android:id="@+id/tv_title"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
          android:minHeight="?attr/actionBarSize"
            android:layout_marginRight="?attr/actionBarSize"
            android:textAppearance="?android:textAppearanceMedium"
            android:textColor="@android:color/black" />

    </android.support.v7.widget.Toolbar>

</LinearLayout>

4. style.xml


<resources>

    <!-- Base application theme. --> 
   <style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
        <!-- Customize your theme here. -->
    </style>
    <style name="ToolBarTheme" parent="Base.ThemeOverlay.AppCompat.Dark.ActionBar">
        <item name="android:textColorPrimary">#000000</item>
        <item name="android:textColorSecondary">#ffffff</item>
    </style>

</resources>

Github Link :- https://github.com/CammyKamal/BlogTutorials/tree/master/AddingToolbar

Google Map integration

In this post we will be having a look on how to integrate Google Maps in android. Here in this post, Google Map is integrating using dynamic fragment.


First Create project on https://console.developers.google.com/ and create a Android key using your SHA1 and Keystore (whether debug or release).

See Below Screenshots as reference for creating a Google web console project. And get your Map API key.










1. MapActivity

import android.location.Geocoder;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.SupportMapFragment;
import com.google.android.gms.maps.model.CameraPosition;
import com.google.android.gms.maps.model.LatLng;

public class MapActivity extends FragmentActivity {
    private GoogleMap googleMap;
    private SupportMapFragment fragment;

    @Override    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_map);
        setupDynamicMapFragment();
    }

    private void setupDynamicMapFragment() {
        FragmentManager fm = this.getSupportFragmentManager();
        fragment = (SupportMapFragment) fm.findFragmentById(R.id.map_container);
        if (fragment == null) {
            fragment = SupportMapFragment.newInstance();
            fm.beginTransaction().replace(R.id.map_container, fragment).commit();
        }
    }

    /*** function to load map. If map is not created it will create it for you     */   
 private void initializeMap() {
        LatLng location = null;
        if (googleMap == null) {
            googleMap = fragment.getMap();
            if (googleMap != null) {
                    location = new LatLng(30.7500,76.7800);
                if (location != null) {
                    CameraPosition cameraPosition = new CameraPosition.Builder().target(
                            location).zoom(12).build();
                    googleMap.animateCamera(CameraUpdateFactory.newCameraPosition(cameraPosition));
                }
            }
        }
    }

//initializing Google Maps on resume in order to let the SupportMapfragment get initialized 
 @Override    protected void onResume() {
        super.onResume();
        initializeMap();
    }
}

2. activity_map.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:id="@+id/map_container"
    tools:context=".MapActivity">

    <!-- The map fragments will go here -->
</RelativeLayout>


3. AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?><manifest 
  xmlns:android="http://schemas.android.com/apk/res/android"    
  package="com.Googlemapdemo" >
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!--  The ACCESS_COARSE/FINE_LOCATION permissions are not required to use  Google Maps Android API v2, but are recommended.     -->
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <!-- Required OpenGL ES 2.0. for Maps V2 -->    
<uses-feature        
 android:glEsVersion="0x00020000"
 android:required="true" />

  <application
    android:allowBackup="true"  
     android:icon="@mipmap/ic_launcher" 
     android:label="@string/app_name" 
     android:theme="@style/AppTheme" >
     <activity        
     android:name=".MapActivity"
     android:label="@string/app_name" >
       <intent-filter>
       <action android:name="android.intent.action.MAIN" />
       <category android:name="android.intent.category.LAUNCHER" />
       </intent-filter>
      </activity>
      <meta-data
       android:name="com.google.android.gms.version"
       android:value="@integer/google_play_services_version" />
     <meta-data
      android:name="com.google.android.maps.v2.API_KEY"
      android:value="@string/google_maps_key" />

 </application>

</manifest>


4. strings.xml


<resources>
    <string name="app_name">MapDemo</string>
    <string name="hello_world">Hello world!</string>
    <string name="action_settings">Settings</string>
    <string name="google_maps_key">YOUR MAP KEY</string>
</resources>

5. build.gradle
apply plugin: 'com.android.application'
android {
    compileSdkVersion 23 
    buildToolsVersion "23.0.0 rc2"
    defaultConfig {
        applicationId "com.mapdemo"
        minSdkVersion 15
        targetSdkVersion 23
        versionCode 1
        versionName "1.0"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.google.android.gms:play-services:7.5.0'}


Output

Wednesday 5 August 2015

Maps in Android Wear

We can show maps in Android wear we display user current location recived from the phone.
Currently we can update user current location from phone only. So this maps on android wear will be helpful for user to see the locations near by when riding.


What is DismissOverlayView? Need to know this first.

In Android wear we Dismiss a screen or view by swiping from left to right. But When we are dealing with Maps in android wear then gestures are overiden by the map gestures so we are not able to perform the normal left to right swipe dismiss action in android wear in maps.

So in order to overcome there is DismissOverlayView which lets you dismiss the map screen view by just long press on the map screen and you will get a alert to quit with cross icon which allows you to dismiss the current screen in view.



And how can we show maps in android wear programmactically is shown below.


Project Structure will be as shown





1. MainActivity.java

public class MainActivity extends FragmentActivity implements OnMapReadyCallback,

        GoogleMap.OnMapLongClickListener,GoogleMap.OnMarkerDragListener {



    private GoogleApiClient mGoogleApiClient;

    private static final LatLng CHANDIGARH = new LatLng(30.7500, 76.7800);

    ArrayList<LatLng> points;

    float distance;

    private DismissOverlayView mDismissOverlay;

    private GoogleMap mMap;



    public void onCreate(Bundle savedState) {

        super.onCreate(savedState);

        setContentView(R.layout.activity_main);

        final FrameLayout topFrameLayout = (FrameLayout) findViewById(R.id.root_container);

        final FrameLayout mapFrameLayout = (FrameLayout) findViewById(R.id.map_container);

        topFrameLayout.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {

            @Override

            public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {

                insets = topFrameLayout.onApplyWindowInsets(insets);

                FrameLayout.LayoutParams params =

                        (FrameLayout.LayoutParams) mapFrameLayout.getLayoutParams();

                params.setMargins(

                        insets.getSystemWindowInsetLeft(),

                        insets.getSystemWindowInsetTop(),

                        insets.getSystemWindowInsetRight(),

                        insets.getSystemWindowInsetBottom());

                mapFrameLayout.setLayoutParams(params);



                return insets;

            }

        });



        mDismissOverlay = (DismissOverlayView) findViewById(R.id.dismiss_overlay);

        mDismissOverlay.setIntroText(R.string.intro_text);

        mDismissOverlay.showIntroIfNecessary();

        SupportMapFragment mapFragment =

                (SupportMapFragment) getSupportFragmentManager()

                        .findFragmentById(R.id.map);

        mapFragment.getMapAsync(this);

    }



    @Override

    public void onMapReady(GoogleMap googleMap) {

        mMap = googleMap;

        mMap.setOnMapLongClickListener(this);

        MarkerOptions marker = new MarkerOptions()

                .position(CHANDIGARH)

                .title("Wear Maps")

                .snippet("" + CHANDIGARH)

                .icon(BitmapDescriptorFactory

                        .defaultMarker(BitmapDescriptorFactory.HUE_GREEN));

        mMap.addMarker(marker);

        mMap.setOnInfoWindowClickListener(new GoogleMap.OnInfoWindowClickListener() {

            @Override

            public void onInfoWindowClick(Marker marker) {



            }

        });

        mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(CHANDIGARH, 14));



    }



    @Override

    public void onMapLongClick(LatLng latLng) {

        mDismissOverlay.show();

    }



    @Override

    public void onMarkerDragEnd(Marker arg0) {

        LatLng dragPosition = arg0.getPosition();

        double dragLat = dragPosition.latitude;

        double dragLong = dragPosition.longitude;

        Log.i("info", "on drag end :" + dragLat + " dragLong :" + dragLong);

        PolylineOptions polylineOptions = new PolylineOptions();

        polylineOptions.color(Color.RED);

        polylineOptions.width(3);

        points.add(dragPosition);

        polylineOptions.addAll(points);

        mMap.addPolyline(polylineOptions);

        Toast.makeText(getApplicationContext(), "Distance ="+distance, Toast.LENGTH_LONG).show();

    }



    @Override

    public void onMarkerDragStart(Marker arg0) {

    }



    @Override

    public void onMarkerDrag(Marker marker) {

    }

}





2. activity_main.xml

<FrameLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:map="http://schemas.android.com/apk/res-auto"

    android:id="@+id/root_container"

    android:layout_height="match_parent"

    android:layout_width="match_parent">





    <FrameLayout

        android:id="@+id/map_container"

        android:layout_width="match_parent"

        android:layout_height="match_parent">



        <fragment

            android:id="@+id/map"

            android:layout_width="match_parent"

            android:layout_height="match_parent"

            android:name="com.google.android.gms.maps.SupportMapFragment"/>



    </FrameLayout>



    <android.support.wearable.view.DismissOverlayView

        android:id="@+id/dismiss_overlay"

        android:layout_height="match_parent"

        android:layout_width="match_parent"/>



</FrameLayout>





3.AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

    package="com.watchmaps.androidsmartwatchmaps" >



    <!-- Permissions and features required for the Android Maps API v2 -->

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

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

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

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

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

    <uses-feature

        android:glEsVersion="0x00020000"

        android:required="true"/>



    <application

        android:allowBackup="true"

        android:icon="@mipmap/ic_launcher"

        android:label="@string/app_name"

        android:theme="@android:style/Theme.DeviceDefault" >

        <!-- API key for the Android Maps API v2. The value is defined as a string resource. -->

        <meta-data android:name="com.google.android.geo.API_KEY"

            android:value="@string/google_maps_key"/>

        <!-- Meta data required for Google Play Services -->

        <meta-data

            android:name="com.google.android.gms.version"

            android:value="@integer/google_play_services_version" />

        <activity

            android:name=".MainActivity"

            android:label="@string/app_name" >

            <intent-filter>

                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>

        </activity>

    </application>



</manifest>





4. build.gradle

apply plugin: 'com.android.application'



android {

    compileSdkVersion 22

    buildToolsVersion "22.0.1"



    defaultConfig {

        applicationId "com.watchmaps.androidsmartwatchmaps"

        minSdkVersion 20

        targetSdkVersion 22

        versionCode 1

        versionName "1.0"

    }

    buildTypes {

        release {

            minifyEnabled false

            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'

        }

    }

}



dependencies {

    compile fileTree(dir: 'libs', include: ['*.jar'])

    compile 'com.google.android.support:wearable:1.2.0'

    compile 'com.google.android.gms:play-services-wearable:7.5.0'

    compile 'com.google.android.gms:play-services-maps:7.5.0'

}



Happy coding
Cheers!!!


References : https://developers.google.com/maps/documentation/android/wear?hl=en

Wednesday 27 May 2015

Designing Layouts for Wearables Android


So while designing layouts for wearable we need to keep both above types in mind so that our layout design looks perfectly fine on both round and square screens.

Android wearable fall's into two categories :

1. Round Shape
2. Square Shape

Now, question is how should we achieve this.Well i encountered some issues on round screen.My screen layout included text and image.So whenever text was large it was cutting in round edges from left side.

So after sometime,i camed to know about BoxInsetLayout which comes under android.support.wearable.view.BoxInsetLayout included in Wearable UI Library .
This helps us define a single layout that works both in round and square screens for wearbles.

Round Shape View


 Square Shape View


Now how to Make use of this lets have a look :




<?xml version="1.0" encoding="utf-8"?>
<android.support.wearable.view.BoxInsetLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffffff"
    android:padding="20dp">

    <android.support.v4.view.ViewPager
        android:id="@+id/myviewpager"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</android.support.wearable.view.BoxInsetLayout>


1. android:padding="20dp" : Now this gives the padding to the BoxInsetLayout from all directions which creates a kind of rectangle for us and we've to work inside it.And this paddingapplies only to square screen of wearable as window insets on round devices are larger than 15 dp.

Note :BoxInsetLayout act as a parent and we have to create our layout inside this parent(like we do inside linear layout etc).

 2. android:padding="5dp" for our child view in this case view pager is our child : This padding applies on both round and square screens.
 
    For Square screen : this makes 20+5 = 25 dp padding
    For Round  screen :this makes 5    = 5 dp padding
 
 3. app:layout_box="all" :This line ensures that the ViewPager element and its children are boxed inside the area defined by the window insets on round screens. This line has no effect on square screens.



References : https://developer.android.com/training/wearables/ui/layouts.html#add-library


happy coding!!
Cheers !!

Monday 25 May 2015

Android Wear Host Disconnected even after running connecting commands

A issue normally that we faced when we try to connect android wear(smartwatch) via android wear companion app

Host   : Disconnected.
Target : Connected.

This normally happens if you've connected with an emulator previously, Then it will cause an issue with the connection.

To solve this just follow below steps :

1. open android wear companion app
2. Go to settings
3. click on Emulator.
4. click on FORGET WATCH

That did the trick for me :-)

Posting by keeping in mind that you guys already know how to connect any wearable by running commands :-)

Cheers!!

Thursday 14 May 2015

Duplicate ID binary XML error in fragments

When dealing Google V2 maps in android tabs and Fragments which generally come across with a issue i.e Duplicate ID binary XML error.

To be more specific Error faced is below :
android.view.InflateException: Binary XML file line #7: Error inflating class fragment

Now why this happens :

Because you already have the fragment added in the FragmentManager, you can't add the same fragment twice which will cause duplication of ID i.e two fragments with same ID.

Now this generally happens when we are adding a fragment that contains a fragment in its layout as well, mostly in case of map fragments.

Our tendency is to add Mapfragment in XML layout like this :

<?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" >

    <fragment

        android:id="@+id/map"

        android:layout_width="match_parent"

        android:layout_height="match_parent"

        class="com.google.android.gms.maps.SupportMapFragment" />



</LinearLayout>


And our Fragment inflate this layout like this :

public class LocationFragment extends Fragment {



View mView;

private static GoogleMap map;

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {

mView = inflater.inflate(R.layout.locationfragment, container, false);

return mView;

}

}


Now if we see closely what happening here. We will add this fragment in a tab, which contains a fragment in its layout as well.This is the reason of problem here.

Note: You cannot inflate a layout into a fragment when that layout includes a <fragment>.Nested fragments are only supported when added to a fragment dynamically.

As per Andorid Documentation this approach is not recommended.We need to add map fragments Dynamically in order to accomplish nested fragments as per android guidelines.Check Nested fragments from below link :

http://developer.android.com/about/versions/android-4.2.html#NestedFragments

So Android-supported way is to add a fragment to another fragment is via a transaction from the child fragment manager as shown below :

XML Layout :

<?xml version="1.0" encoding="utf-8"?>

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"

xmlns:map="http://schemas.android.com/apk/res-auto"

   android:layout_width="match_parent"

   android:layout_height="match_parent" >

<!-- Lots of fancy layout -->  
<RelativeLayout
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </RelativeLayout>
</RelativeLayout>


Java Code Snippet :

public class MyFragment extends Fragment {

private SupportMapFragment fragment;
private GoogleMap map;

@Override

public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.layout_with_map, container, false);
}

@Override
public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    FragmentManager fm = getChildFragmentManager();
    fragment = (SupportMapFragment) fm.findFragmentById(R.id.map);
    if (fragment == null) {
        fragment = SupportMapFragment.newInstance();
       fm.beginTransaction().replace(R.id.map, fragment).commit();
    }
}



@Override
public void onResume() {
    super.onResume();
    if (map == null) {
        map = fragment.getMap();
        map.addMarker(new MarkerOptions().position(new LatLng(0, 0)));
    }
}
}

There are other ways to resolve the errors as well if you are willing to you map fragment inside a XML layout.Below are the ways to solve the error :

1. Remove fragment on onDestroyView() :

@Override
public void onDestroyView() {
    super.onDestroyView();
    MapFragment f = (MapFragment) getFragmentManager()
                                         .findFragmentById(R.id.map);

    if (f != null)
        getFragmentManager().beginTransaction().remove(f).commit();
}



2. Remove the parent view if it exists in onCreateView() :

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    if (view != null) {
        ViewGroup parent = (ViewGroup) view.getParent();
        if (parent != null)
            parent.removeView(view);
    }
    try {
        view = inflater.inflate(R.layout.map, container, false);
    } catch (InflateException e) {
    }
    return view;
}
The above two methods will resolve our issue of duplicate ID in map fragment when dealing with fragments and tabs, but its kind of "Jugaad"(tricky way) as we say in HINDI.So this might be working now but might not working in upcoming versions of android.So better way to go is as Android Recommends.


References : http://developer.android.com/about/versions/android-4.2.html#NestedFragments , www.stackoverflow.com.

Happy Coding!!
Cheers.

Wednesday 13 May 2015

Demo for Android v2 Maps Allowing Free hand drawing for user

First create a project on google apis web console. And get the API KEY for v2 maps. 

Include google-play-services_lib in your project.

 1.MainActivity.java

public class MainActivity extends FragmentActivity implements OnMapLongClickListener,OnMapClickListener,OnMarkerDragListener {

 private static GoogleMap map;
 ArrayList points;
 LatLng mLatlng;
 int i=0;
 float distance;
 @Override
 protected void onCreate(Bundle saveInstance) {
  super.onCreate(saveInstance);

  requestWindowFeature(Window.FEATURE_NO_TITLE);
  setContentView(R.layout.activity_main);
  points = new ArrayList();
  map = ((SupportMapFragment)getSupportFragmentManager().findFragmentById(R.id.map)).getMap();
  map.setOnMarkerDragListener(this);
  map.setOnMapLongClickListener(this);
  map.setOnMapClickListener(this);
  mLatlng=new LatLng(30.7500, 76.7800 );
  CameraPosition INIT =
    new CameraPosition.Builder()
  .target(new LatLng(30.7500, 76.7800))
  .zoom(17.5F)
  .bearing(300F) // orientation
  .tilt( 50F) // viewing angle
  .build();
  // use map to move camera into position
  map.moveCamera( CameraUpdateFactory.newCameraPosition(INIT) );
  points.add(new LatLng(30.7500, 76.7800));
  //create initial marker
  map.addMarker( new MarkerOptions()
  .position( new LatLng(30.7500, 76.7800) )
  .title("Location").draggable(true)
  .snippet("First Marker")).showInfoWindow();
 }

 @Override
 public void onMarkerDrag(Marker arg0) {
  LatLng dragPosition = arg0.getPosition();
  double dragLat = dragPosition.latitude;
  double dragLong = dragPosition.longitude;
  Log.i("info", "on drag end :" + dragLat + " dragLong :" + dragLong);
  PolylineOptions polylineOptions = new PolylineOptions();
  // Setting the color of the polyline
  polylineOptions.color(Color.RED);
  // Setting the width of the polyline
  polylineOptions.width(3);
  // Adding the taped point to the ArrayList 
  points.add(dragPosition);
  // Setting points of polyline
  polylineOptions.addAll(points);
  // Adding the polyline to the map
  map.addPolyline(polylineOptions);
  Location locationA = new Location("point A");     
  locationA.setLatitude(mLatlng.latitude); 
  locationA.setLongitude(mLatlng.longitude);
  Location locationB = new Location("point B");
  locationB.setLatitude(dragLat); 
  locationB.setLongitude(dragLong);
  distance = locationA.distanceTo(locationB) ;
  mLatlng=new LatLng(dragLat, dragLong);
 }

 @Override
 public void onMarkerDragEnd(Marker arg0) {
  LatLng dragPosition = arg0.getPosition();
  double dragLat = dragPosition.latitude;
  double dragLong = dragPosition.longitude;
  Log.i("info", "on drag end :" + dragLat + " dragLong :" + dragLong);
  PolylineOptions polylineOptions = new PolylineOptions();
  // Setting the color of the polyline
  polylineOptions.color(Color.RED);
  // Setting the width of the polyline
  polylineOptions.width(3);
  // Adding the taped point to the ArrayList 
  points.add(dragPosition); 
  // Setting points of polyline
  polylineOptions.addAll(points);
  // Adding the polyline to the map
  map.addPolyline(polylineOptions);
  Toast.makeText(getApplicationContext(), "Distance ="+distance, Toast.LENGTH_LONG).show();
 }

 @Override
 public void onMarkerDragStart(Marker arg0) {

 }

 @Override
 public void onMapClick(LatLng arg0) {
  map.animateCamera(CameraUpdateFactory.newLatLng(arg0));
 }


 @Override
 public void onMapLongClick(LatLng arg0) {
 }   
}
XML layouts :

1. activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity" >

    <fragment
        android:id="@+id/map"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        class="com.google.android.gms.maps.SupportMapFragment" />

</RelativeLayout>

2. AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.kamal"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="21" />

    <permission
        android:name="com.kamal.permission.MAPS_RECEIVE"
        android:protectionLevel="signature" />
    <uses-permission android:name="com.kamal.permission.MAPS_RECEIVE" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-feature
        android:glEsVersion="0x00020000"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name="com.kamal.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <meta-data
            android:name="com.google.android.maps.v2.API_KEY"
            android:value="AIzaSyAchnHfCbb35xPnFfd-dbQ72kQ7glqvVzg" />
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    </application>

</manifest>


OutPut :