Main navigation

Android Content Provider Tutorial

adsense

Pre-requisites for Android Content Provider Tutorial

1) Android Studio installed on your PC (Unix or Windows). You can learn how to install it here .
2) A real time android device (Smartphone or Tablet) configured with Android Studio. .
3) Go through the SQLite Database Tutorial in case you are not aware of the SQLite Database concepts.

We are using the following structure for the contacts.db Database.

Table Name : contacts

FieldTypeKey
_idINTPRIMARY
contactNameTEXT 
contactPhoneTEXT 
contactCreationTimeStampTEXT 

Let’s first create the My Contacts that will create a custom Android Content Provider

Creating a New Project

  1. Open Android Studio and create a new project My Contacts and company domain application.example.com (We have used our own company domain i.e androidtutorialpoint.com. Similarly you can use yours).
  2. Click Next and choose Min SDK, we have kept the default value. Again Click Next and Choose Basic Activity.
  3. Choose the Activity as MainActivity and click next.
  4. Leave all other things as default and Click Finish.

Create SQLite Database for My Contacts App

Create a new Java class and add the following code. This is our DBHelper class, here we are creating the Contacts Database. For more details please refer the explanation provided after EmployeeDBHandler class in SQLite Database Tutorial. Here we will concentrate mainly on the Android Content Provider concepts/

DBOpenHelper.java

package com.androidtutorialpoint.mycontacts;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBOpenHelper extends SQLiteOpenHelper {

    //Constants for db name and version
    private static final String DATABASE_NAME = "contacts.db";
    private static final int DATABASE_VERSION = 1;

    //Constants for table and columns
    public static final String TABLE_CONTACTS = "contacts";
    public static final String CONTACT_ID = "_id";
    public static final String CONTACT_NAME = "contactName";
    public static final String CONTACT_PHONE = "contactPhone";
    public static final String CONTACT_CREATED_ON = "contactCreationTimeStamp";

    public static final String[] ALL_COLUMNS =
            {CONTACT_ID,CONTACT_NAME,CONTACT_PHONE,CONTACT_CREATED_ON};

    //Create Table
    private static final String CREATE_TABLE =
            "CREATE TABLE " + TABLE_CONTACTS + " (" +
                    CONTACT_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    CONTACT_NAME + " TEXT, " +
                    CONTACT_PHONE + " TEXT, " +
                    CONTACT_CREATED_ON + " TEXT default CURRENT_TIMESTAMP" +
                    ")";
    public DBOpenHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_TABLE);
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        sqLiteDatabase.execSQL("DROP TABLE IF EXISTS "+ TABLE_CONTACTS);
        onCreate(sqLiteDatabase);
    }
}

Create Custom Android Content Provider

Create a new Java class ContactsProvider.java and put the following code.

ContactsProvider.java

package com.androidtutorialpoint.mycontacts;

import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.support.annotation.Nullable;

public class ContactsProvider extends ContentProvider {

    private static final String AUTHORITY = "com.androidtutorialpoint.mycontacts";
    private static final String BASE_PATH = "contacts";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH );

    private static final int CONTACTS = 1;
    private static final int CONTACT_ID = 2;

    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        uriMatcher.addURI(AUTHORITY,BASE_PATH, CONTACTS);
        uriMatcher.addURI(AUTHORITY,BASE_PATH + "/#",CONTACT_ID);
    }

    private SQLiteDatabase database;

    @Override
    public boolean onCreate() {
        DBOpenHelper helper = new DBOpenHelper(getContext());
        database = helper.getWritableDatabase();
        return true;
    }

    @Nullable
    @Override
    public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
        Cursor cursor;
        switch (uriMatcher.match(uri)) {
            case CONTACTS:
                cursor =  database.query(DBOpenHelper.TABLE_CONTACTS,DBOpenHelper.ALL_COLUMNS,
                        s,null,null,null,DBOpenHelper.CONTACT_NAME +" ASC");
                break;
            default:
                throw new IllegalArgumentException("This is an Unknown URI " + uri);
        }
        cursor.setNotificationUri(getContext().getContentResolver(),uri);

        return cursor;
    }

    @Nullable
    @Override
    public String getType(Uri uri) {

        switch (uriMatcher.match(uri)) {
            case CONTACTS:
                return "vnd.android.cursor.dir/contacts";
            default:
                throw new IllegalArgumentException("This is an Unknown URI " + uri);
        }
    }

    @Nullable
    @Override
    public Uri insert(Uri uri, ContentValues contentValues) {
        long id = database.insert(DBOpenHelper.TABLE_CONTACTS,null,contentValues);

        if (id > 0) {
            Uri _uri = ContentUris.withAppendedId(CONTENT_URI, id);
            getContext().getContentResolver().notifyChange(_uri, null);
            return _uri;
        }
        throw new SQLException("Insertion Failed for URI :" + uri);

    }

    @Override
    public int delete(Uri uri, String s, String[] strings) {
        int delCount = 0;
        switch (uriMatcher.match(uri)) {
            case CONTACTS:
                delCount =  database.delete(DBOpenHelper.TABLE_CONTACTS,s,strings);
                break;
            default:
                throw new IllegalArgumentException("This is an Unknown URI " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return delCount;
    }

    @Override
    public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
        int updCount = 0;
        switch (uriMatcher.match(uri)) {
            case CONTACTS:
                updCount =  database.update(DBOpenHelper.TABLE_CONTACTS,contentValues,s,strings);
                break;
            default:
                throw new IllegalArgumentException("This is an Unknown URI " + uri);
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return updCount;
    }
}

To create an Android Content Provider we need to provide a content URI which is a unique identifier for a provider across all apps.

Below image shows explains the structure of Android Content provider URI .

Android Content Provider ContentURI Description

Android Content Provider ContentURI Description

Next, We have UriMatcher helper class for matching the URIs in our custom android content provider. It’s match(Uri) is used in the Switch construct to determine the operation to be performed based upon the two added Uri’s.

In the onCreate we are getting a Database Object.

Here is the brief explanation of the Android Content Provider Class methods we are overriding.

To get the data stored in the SQLite DB via content provider we need to override the query() method of Android Content Provider class.

We pass the following arguments to the query() method and get a cursor object.

Cursor query (  
                Uri uri, // Uri 
                String[] projection, // Which columns to return or null for all columns.
                String selection, // WHERE clause or null for no filter
                String[] selectionArgs,   // WHERE clause value substitution
                String sortOrder // Sort order or null
            ) 

returns a Cursor or null;

The insert method takes in uri and contentValues object and returns a Uri.

Uri insert (
                Uri uri, // URI of the insertion request. can't be null.
                ContentValues values // column/value pairs to be added to storage, can't be null
            )

returns the Uri of the newly inserted item.

int update (
                Uri uri, //The URI of query, will have a record ID if you want to update one record. 
                ContentValues values, // column/value pairs to be updated in storage, can't be null
                String selection, // optional WHERE clause to match the rows to be updated 
                String[] selectionArgs // WHERE clause value substitution
            )

returns the number of rows affected.

int delete (
                Uri uri, //The URI of the query, will have a record ID if you want to delete one record.
                String selection, // optional WHERE clause to match the rows to be deleted 
                String[] selectionArgs // WHERE clause value substitution
            )

returns the number of rows affected.

String getType (
                    Uri uri //Uri of the query
                )

returns the MIME type string, or null.

In the implementation of these methods, we are trying to match the Uri passed to the method, in case the Uri matches we are processing the information otherwise we are using error handling code to raise an exception.

Listing the Contacts in Provider App

We will be listing the all the contacts in the My Contacts app. For this we will use the Cursor Adaptor. Create a new file ContactsCursorAdapter.java and put the following code.

ContactsCursorAdapter.java

package com.androidtutorialpoint.mycontacts;

import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;

public class ContactsCursorAdapter extends CursorAdapter {
    public ContactsCursorAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {

        return LayoutInflater.from(context).inflate(
                R.layout.contact_list_item,viewGroup,false );
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        String contactName = cursor.getString(
                cursor.getColumnIndex(DBOpenHelper.CONTACT_NAME));
        String contactPhone = cursor.getString(
                cursor.getColumnIndex(DBOpenHelper.CONTACT_PHONE));
        TextView nameTextView = (TextView) view.findViewById(R.id.nameTextView);
        TextView phoneTextView = (TextView) view.findViewById(R.id.phoneTextView);
        nameTextView.setText(contactName);
        phoneTextView.setText(contactPhone);

    }
}

The code is self explanatory. In the newView() method, we are inflating the layout contact_list_item.xml which represents layout for a single entry in our List. Then in the bindView() method we are getting the values from the cursor and setting it to the nameTextView and phoneTextView.

Here is the layout for the ContactsCursorAdapter.java

contact_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:gravity="center_vertical"
    android:padding="4dp">

    <ImageView
        android:id="@+id/imageDocIcon"
        android:layout_width="72dp"
        android:layout_height="72dp"
        android:src="@drawable/profile_pic" />

    <TextView
        android:id="@+id/nameTextView"
        android:layout_width="wrap_content"
        android:layout_height="36dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_marginTop="5dp"
        android:layout_marginLeft="15dp"
        android:layout_toEndOf="@+id/imageDocIcon"
        android:layout_toRightOf="@+id/imageDocIcon"
        android:text="Dummy Text"
        android:textColor="@android:color/black"
        android:textSize="20sp"
        android:textStyle="bold"/>

    <TextView
        android:id="@+id/phoneTextView"
        android:layout_width="match_parent"
        android:layout_height="36dp"
        android:layout_alignParentEnd="true"
        android:layout_alignParentRight="true"
        android:layout_marginLeft="15dp"
        android:layout_toEndOf="@+id/imageDocIcon"
        android:layout_toRightOf="@+id/imageDocIcon"
        android:layout_below="@+id/nameTextView"
        android:text="Dummy Text"
        android:textColor="@android:color/black"
        android:textSize="15sp" />
</RelativeLayout>

Layout consist of an ImageView for a profile Pic and two TextView for Name and Phone number respectively.

Now let’s add the code for the MainActivity.

MainActivity.java

package com.androidtutorialpoint.contactslistapp;

import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.CursorAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
    private CursorAdapter cursorAdapter;
    private static final String AUTHORITY = "com.androidtutorialpoint.mycontacts";
    private static final String BASE_PATH = "contacts";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH );

    // Constant to identify the requested operation
    private static final int CONTACTS = 1;
    private static final int CONTACT_ID = 2;

    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        uriMatcher.addURI(AUTHORITY,BASE_PATH, CONTACTS);
        uriMatcher.addURI(AUTHORITY,BASE_PATH + "/#",CONTACT_ID);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        cursorAdapter = new ContactsCursorAdapter(this,null,0);
        ListView list = (ListView) findViewById(android.R.id.list);
        list.setAdapter(cursorAdapter);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getLoaderManager().initLoader(0, null, this);
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                restartLoader();

            }
        });
    }

   private void restartLoader() {
        getLoaderManager().restartLoader(0,null,this);
    }


    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        return new CursorLoader(this,CONTENT_URI,null,null,null,null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        cursorAdapter.swapCursor(cursor);

    }
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        cursorAdapter.swapCursor(null);
    }

}

In the MainActivity.java we are implementing the LoaderManagerLoaderCallbacks interface to manage our CursorAdapter

In the onCreate() method we are creating a new ContactsCursorAdapter and then setting it to the ListView from the layout. On clicking the FloatingActionButton we are invoking an AlertDialog to input the Contact’s Name and Phone number. If you want to learn more about working of AlertDialog, please visit the following tutorial Android Alert Dialogs Tutorial

On clicking the Add button on the Dialog, insertContact() method is called, which uses the insert() method of our custom Android Content Provider to insert a new record in the database.

Similarly, we have an option in the Menu to delete all the existing contacts by calling the delete() method of our custom Android Content Provider without any filters.

Next, we have implemented the methods for the LoaderManager.LoaderCallbacks interface

  1. restartLoader(int id, Bundle args, LoaderCallbacks callback) : It starts a new or restarts an existing Loader in this manager, registers the callbacks to it, and starts loading it.
  2. onCreateLoader(int id, Bundle args) : This methods creates and return a new Loader for the given id .
  3. onLoadFinished(Loader loader, Cursor cursor) : This method is called when a previously created loader is finished loading. We are returning a previously set cursor using the swapCursor method.
  4. onLoaderReset(Loader loader) : This method is called when a previously created loader is being reset. We are returning a previously set cursor using the swapCursor method.

Add the following layout for the MainActivity.java

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.androidtutorialpoint.mycontacts.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_height="wrap_content"
        android:layout_width="match_parent"
        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>
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">
        <ListView
            android:layout_marginTop="70dp"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/android:list"/>
    </RelativeLayout>
    <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"
        app:srcCompat="@android:drawable/ic_input_add" />

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

Creating Permissions for the custom Android Content Provider

We need to provide an entry for our custom Android Content Provider in the AndroidManifest.xml

Open your AndroidManifest.xml and add the permissions and provider entry as in the following code.

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.androidtutorialpoint.mycontacts">
    <permission android:name="com.androidtutorialpoint.mycontacts.READ_DATABASE" android:protectionLevel="normal" />
    <permission android:name="com.androidtutorialpoint.mycontacts.WRITE_DATABASE" android:protectionLevel="normal" />
    <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>
        <provider
            android:authorities="com.androidtutorialpoint.mycontacts"
            android:name=".ContactsProvider"
            android:exported="true"
            android:readPermission="com.androidtutorialpoint.mycontacts.READ_DATABASE"
            android:writePermission="com.androidtutorialpoint.mycontacts.WRITE_DATABASE"
            />
    </application>
</manifest>

we need to provide the authority and read and write permissions here.
using android:exported=”true” our provider becomes available to other applications.

Now run the app and add few contacts. It should create a list sorted by the Name in the alphabetical order like this.

My Contacts App for Android Content Provider Tutorial

Creating the Consumer App

  1. Open Android Studio and create a new project Contacts List App and company domain application.example.com (We have used our own company domain i.e androidtutorialpoint.com. Similarly you can use yours).
  2. Click Next and choose Min SDK, we have kept the default value. Again Click Next and Choose Basic Activity.
  3. Choose the Activity as MainActivity and click next.
  4. Leave all other things as default and Click Finish.

Add the following permissions to use the ContactsProvider in the AndroidManifest.xml

AndroidManifest.xml

    <uses-permission android:name="com.androidtutorialpoint.mycontacts.READ_DATABASE" />
    <uses-permission android:name="com.androidtutorialpoint.mycontacts.WRITE_DATABASE" />

Add the following code to the MainActivity

MainActivity.java

package com.androidtutorialpoint.contactslistapp;

import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.content.UriMatcher;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.View;
import android.widget.CursorAdapter;
import android.widget.ListView;

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor>{
    private CursorAdapter cursorAdapter;
    private static final String AUTHORITY = "com.androidtutorialpoint.mycontacts";
    private static final String BASE_PATH = "contacts";
    public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/" + BASE_PATH );

    // Constant to identify the requested operation
    private static final int CONTACTS = 1;
    private static final int CONTACT_ID = 2;

    private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
    static {
        uriMatcher.addURI(AUTHORITY,BASE_PATH, CONTACTS);
        uriMatcher.addURI(AUTHORITY,BASE_PATH + "/#",CONTACT_ID);
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        cursorAdapter = new ContactsCursorAdapter(this,null,0);
        ListView list = (ListView) findViewById(android.R.id.list);
        list.setAdapter(cursorAdapter);

        Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);
        getLoaderManager().initLoader(0, null, this);
        FloatingActionButton fab = (FloatingActionButton) findViewById(R.id.fab);
        fab.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                restartLoader();

            }
        });
    }

   private void restartLoader() {
        getLoaderManager().restartLoader(0,null,this);
    }
    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        return new CursorLoader(this,CONTENT_URI,null,null,null,null);
    }
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        cursorAdapter.swapCursor(cursor);

    }
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        cursorAdapter.swapCursor(null);
    }
}

The code for MainActivity is pretty much same as the My Contacts app, We have just removed the code for inserting and deleting the Contacts and we are just displaying those records.

Reuse the layout activity_main.xml from the My Contacts and change the icon for the Floating Button from app:srcCompat=”@android:drawable/ic_input_add” to app:srcCompat=”@android:drawable/ic_popup_sync” to reflect that we want to sync the data.

On clicking the Floating Button we are just calling restartLoader() to update the list.

Here is the code for ContactsCursorAdapter for this app. It is mostly same, however, we have added constants for the name and phone.

package com.androidtutorialpoint.contactslistapp;

import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.TextView;

public class ContactsCursorAdapter extends CursorAdapter{

    public static final String CONTACT_NAME = "contactName";
    public static final String CONTACT_PHONE = "contactPhone";

    public ContactsCursorAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup viewGroup) {

        return LayoutInflater.from(context).inflate(
                R.layout.contact_list_item,viewGroup,false );
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        String contactName = cursor.getString(
                cursor.getColumnIndex(CONTACT_NAME));
        String contactPhone = cursor.getString(
                cursor.getColumnIndex(CONTACT_PHONE));
        TextView nameTextView = (TextView) view.findViewById(R.id.nameTextView);
        TextView phoneTextView = (TextView) view.findViewById(R.id.phoneTextView);
        nameTextView.setText(contactName);
        phoneTextView.setText(contactPhone);

    }
}

You can reuse the layout contact_list_item.xml from the My Contacts
app.
Now run the List Contacts it should reflect the contacts created earlier.

List Contacts App for Android Content Provider Tutorial

You can get the drawable and String resources used for these projects by downloading the projects.



What’s Next

We hope this tutorial will really help you creating custom Android content provider. You can read about the Content Provider available in Android SDK and try to use them in your app. We will soon be covering more such tutorials. Till then stay tuned and don’t forget to subscribe our blog for latest android tutorials. Also do Like our Facebook Page or Add us on Twitter.

Click on the Download Now button to download the full code.

Android Tutorial Point Download Now


Reader Interactions

Comments

  1. Thank you very much for sharing this tutorial, but there are some situations that I want to comment you:
    1. The code of the MainActivity that you explain in this article for “My Contacts” app is wrong, checking your full code I realized it wasn’t the same.
    2. For “My Contacts” app some layouts like the alert dialog and the menu aren’t explained, in addition there’s no instructions to incorporate them.
    3. For “My Contacts” apps the strings and dimens xml files aren’t explained (in your full code you use certain variables of them).

    The tutorial is really good and thanks again for that, but if you can I suggest adding the three previous points, because I had to make a depth review to your full source code in order to identify why the app was not working correctly, it would be great for future people to avoid that.

Leave a Reply

Advertisment ad adsense adlogger