3

I have three tables and would like to use a content provider to manage them.Below is code from my content provider:

private static final UriMatcher sURIMatcher = new UriMatcher(
        UriMatcher.NO_MATCH);
static {
    sURIMatcher.addURI(AUTHORITY, METER_PATH, all_meters);
    sURIMatcher.addURI(AUTHORITY, METER_PATH + "/#", single_meter);

    sURIMatcher.addURI(AUTHORITY, CUSTOMERS_PATH, all_customers);
    sURIMatcher.addURI(AUTHORITY, CUSTOMERS_PATH + "/#", single_customer);

    sURIMatcher.addURI(AUTHORITY, BILL_PATH, all_bills);
    sURIMatcher.addURI(AUTHORITY, BILL_PATH + "/#", single_bill);

}

@Override
public Cursor query(Uri uri, String[] projection, String selection,
        String[] selectionArgs, String sortOrder) {

    SQLiteDatabase db = database.getWritableDatabase();

    // Using SQLiteQueryBuilder instead of query() method
    SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

queryBuilder
            .setTables(MeterTableDetails.TABLE_METERS
                    + " as meters "
                    + " INNER JOIN "
                    + CustomerTableDetails.TABLE_CUSTOMERS
                    + " as customers "
                    + " ON "
                    + (MeterTableDetails.METER_ID = CustomerTableDetails.KEY_METER_ID)
                    + " INNER JOIN "
                    + WaterBillTableDetails.TABLE_WATER_BILL
                    + " as waterbills "
                    + " ON "
                    + (CustomerTableDetails.KEY_METER_ID = WaterBillTableDetails.BILL_METER_ID));
    int uriType = sURIMatcher.match(uri);
    switch (uriType) {

    case all_meters:
        break;
    case single_meter:
        // Adding the ID to the original query

        String id = uri.getPathSegments().get(1);
        queryBuilder.appendWhere(MeterTableDetails.METER_ID + "=" + id);

        break;

    case all_customers:
        break;
    case single_customer:
        // Adding the ID to the original query
        String id1 = uri.getPathSegments().get(1);
        queryBuilder.appendWhere(CustomerTableDetails.KEY_CUSTOMER_ID + "="
                + id1);
        break;

    case all_bills:
        break;
    case single_bill:
        // Adding the ID to the original query
        String id2 = uri.getPathSegments().get(1);
        queryBuilder.appendWhere(WaterBillTableDetails.BILL_ID + "=" + id2);
        break;

    default:
        throw new IllegalArgumentException("Unknown URI: " + uri);
    }

    Cursor cursor = queryBuilder.query(db, projection, selection,
            selectionArgs, null, null, sortOrder);
    // Make sure that potential listeners are getting notified
    cursor.setNotificationUri(getContext().getContentResolver(), uri);

    return cursor;
} 

I have three tables,and have created some joins in the querybuilder.setTables method.I'm trying to display meter items in a list from the meters table.I also have a SimpleCursorAdapter with loaderCallbacks implementation. Currently i get the following error in my logcat and i think it's because of the joins and the query:

03-20 15:11:59.692: E/SQLiteCursor(2001): requesting column name with table name -- meters._id
03-20 15:11:59.692: E/SQLiteCursor(2001): java.lang.Exception
03-20 15:11:59.692: E/SQLiteCursor(2001):   at android.database.sqlite.SQLiteCursor.getColumnIndex(SQLiteCursor.java:180)
03-20 15:11:59.692: E/SQLiteCursor(2001):   at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java:301)
03-20 15:11:59.692: E/SQLiteCursor(2001):   at android.database.CursorWrapper.getColumnIndexOrThrow(CursorWrapper.java:78)
03-20 15:11:59.692: E/SQLiteCursor(2001):   at android.support.v4.widget.SimpleCursorAdapter.findColumns(SimpleCursorAdapter.java:317)
03-20 15:11:59.692: E/SQLiteCursor(2001):   at android.support.v4.widget.SimpleCursorAdapter.swapCursor(SimpleCursorAdapter.java:328)
03-20 15:11:59.692: E/SQLiteCursor(2001):   at com.isys.waterbillingsystem.MetersActivity.onLoadFinished(MetersActivity.java:180)
03-20 15:11:59.692: E/SQLiteCursor(2001):   at com.isys.waterbillingsystem.MetersActivity.onLoadFinished(MetersActivity.java:1)

EDIT

private static final String CREATE_CUSTOMER_VIEW = ""
        + "CREATE VIEW " + TABLE_CUSTOMER_VIEW 
        + " AS SELECT "+MeterTableDetails.TABLE_METERS+"."+MeterTableDetails.METER_ID+" AS "+ MeterTableDetails.TABLE_METERS+"."+MeterTableDetails.METER_ID +","+
        " "+CustomerTableDetails.KEY_FIRST_NAME+","+
        " "+CustomerTableDetails.KEY_LAST_NAME+","+
        " "+CustomerTableDetails.KEY_METER_ID+","+
        " "+CustomerTableDetails.KEY_METER_NUMBER+","+
        " "+CustomerTableDetails.KEY_PLOT_NUMBER+","+
        " "+CustomerTableDetails.TABLE_CUSTOMERS+"."+ CustomerTableDetails.KEY_CUSTOMER_ID+
        " FROM "+CustomerTableDetails.TABLE_CUSTOMERS+" AS customers "+" INNER JOIN "+MeterTableDetails.TABLE_METERS+" AS meters"+
        " ON "+CustomerTableDetails.KEY_METER_ID+" = "+MeterTableDetails.TABLE_METERS+"."+MeterTableDetails.METER_ID;


 public static TableDescriptor getDescriptor() {
    TableDescriptor descriptor = new TableDescriptor();
    descriptor.setTableName(TABLE_CUSTOMER_VIEW);
    descriptor.setColumnId(CUSTOMER_VIEW_ID);
    String[] available = { ViewCustomers.CUSTOMER_VIEW_ID,
            ViewCustomers.CUSTOMER_VIEW_LASTNAME,
            ViewCustomers.CUSTOMER_VIEW_LASTNAME,
            ViewCustomers.CUSTOMER_VIEW_KEY_METER_ID,
            ViewCustomers.CUSTOMER_VIEW_METER,
            ViewCustomers.CUSTOMER_VIEW_PLOT};

    descriptor.setAvailableColumns(available);
    return descriptor;
    }    

EDIT 2

private static final String CREATE_METER_READING_VIEW = ""
        + "CREATE VIEW " + TABLE_METER_READING_VIEW
        + " AS SELECT " + WaterBillTableDetails.TABLE_WATER_BILL+ ".*"
        + ", " +CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_METER_NUMBER+","
        +" "+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_PLOT_NUMBER+","
        +" "+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_ACCOUNT_NUMBER+","
         +" "+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_METER_ID+""
        +" FROM "+WaterBillTableDetails.TABLE_WATER_BILL+" AS waterbills "+" JOIN "+CustomerTableDetails.TABLE_CUSTOMERS+" AS customers"
        +" ON "+WaterBillTableDetails.BILL_CUSTOMER_ID+" ="+CustomerTableDetails.TABLE_CUSTOMERS+"."+CustomerTableDetails.KEY_CUSTOMER_ID;

Logcat error

03-25 10:45:03.476: E/AndroidRuntime(1144): FATAL EXCEPTION: main
03-25 10:45:03.476: E/AndroidRuntime(1144): java.lang.RuntimeException: Unable to start activity  ComponentInfo{com.isys.waterbillingsystem/com.isys.waterbillingsystem.CustomerDetailsAccountsActivity}:   java.lang.NullPointerException
03-25 10:45:03.476: E/AndroidRuntime(1144): Caused by: java.lang.NullPointerException
03-25 10:45:03.476: E/AndroidRuntime(1144):     at  com.isys.waterbillingsystem.CustomerDetailsAccountsActivity.onCreate(CustomerDetailsAccountsActivity.java:48 )
naffie
  • 679
  • 1
  • 12
  • 29
  • I'll try to reproduce your code a later today in order to see what causes the error. Meanwhile - have you tried using Views? I find them quite useful since a lot of Android's DB management is easier to use when working with a single table. – Samuil Yanovski Mar 21 '14 at 09:50
  • Thank you so much @SamuilYanovski !..this thing has really been bugging me for days..i've read about the views...but don't really know how to implement them.Do you mind showing me how the different scenarios would work?Both the content provider and the views so that i see the difference. – naffie Mar 21 '14 at 09:55

1 Answers1

6

Here is a quick example for Views:

public class HektorDatabaseHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "hektor.db";
    private static final int DATABASE_VERSION = 91;

    public HektorDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    // Method is called during creation of the database
    @Override
    public void onCreate(SQLiteDatabase database) {
        AppointmentTypesTable.onCreate(database);
    }

    // Method is called during an upgrade of the database,
    // e.g. if you increase the database version
    @Override
    public void onUpgrade(SQLiteDatabase database, int oldVersion,
            int newVersion) {
        AppointmentTypesTable.onUpgrade(database, oldVersion, newVersion);
    }


}

This is a standard SQLiteOpenHelper.

    public class AppointmentWithTypeAndContactsView {

    public static final String TABLE_NAME = "appointments_with_type_and_contacts";

    public static final String COLUMN_ID = AppointmentsTable.COLUMN_ID;
    public static final String COLUMN_EXTERNAL_ID = AppointmentsTable.COLUMN_EXTERNAL_ID;
    public static final String COLUMN_START_DATE = AppointmentsTable.COLUMN_START_DATE;

    private static final String DATABASE_CREATE = ""
            + "CREATE VIEW " + TABLE_NAME 
            + " AS SELECT " + AppointmentsTable.TABLE_NAME + ".*"
            + ", " + AppointmentTypesTable.TABLE_NAME + "." + AppointmentTypesTable.COLUMN_NAME
            + ", " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_TITLE + " || ' ' || " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_LAST_NAME + " || ' ' || " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_FIRST_NAME + " AS " + BuyersTable.COLUMN_LAST_NAME
            + ", " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_TITLE + " || ' ' || " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_LAST_NAME + " || ' ' || " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_FIRST_NAME + " AS " + SellersDetailsTable.COLUMN_LAST_NAME
            + " FROM " + AppointmentsTable.TABLE_NAME + " LEFT OUTER JOIN " + AppointmentTypesTable.TABLE_NAME
            + " ON " + AppointmentsTable.TABLE_NAME + "." + AppointmentsTable.COLUMN_TYPE 
            + " = " + AppointmentTypesTable.TABLE_NAME + "." + AppointmentTypesTable.COLUMN_EXTERNAL_ID
            + " LEFT OUTER JOIN " + BuyersTable.TABLE_NAME
            + " ON " +  AppointmentsTable.TABLE_NAME + "." + AppointmentsTable.COLUMN_BUYER
            + " = " + BuyersTable.TABLE_NAME + "." + BuyersTable.COLUMN_EXTERNAL_ID
            + " LEFT OUTER JOIN " + SellersDetailsTable.TABLE_NAME
            + " ON " +  AppointmentsTable.TABLE_NAME + "." + AppointmentsTable.COLUMN_SELLER
            + " = " + SellersDetailsTable.TABLE_NAME + "." + SellersDetailsTable.COLUMN_EXTERNAL_ID;



    public static void onCreate(SQLiteDatabase database) {
        database.execSQL(DATABASE_CREATE);
    }

    public static void onUpgrade(SQLiteDatabase database, int oldVersion,
            int newVersion) {
        Log.w(AppointmentWithTypeAndContactsView.class.getName(), "Upgrading database from version "
                + oldVersion + " to " + newVersion
                + ", which will destroy all old data");
        database.execSQL("DROP VIEW IF EXISTS " + TABLE_NAME);
        onCreate(database);
    }

    public static TableDescriptor getDescriptor() {
        TableDescriptor descriptor = new TableDescriptor();
        descriptor.setTableName(TABLE_NAME);
        descriptor.setColumnId(COLUMN_ID);

        String[] appointmentsAvailableColumns = AppointmentsTable.getDescriptor().getAvailableColumns();
        String[] typesAvailableColumns = new String[] {AppointmentTypesTable.COLUMN_NAME};
        String[] buyersAvailableColumns = new String[] {BuyersTable.COLUMN_LAST_NAME};
        String[] sellerssAvailableColumns = new String[] {SellersDetailsTable.COLUMN_LAST_NAME};

        descriptor.setAvailableColumns(ArrayUtils.concatAll(appointmentsAvailableColumns, typesAvailableColumns, buyersAvailableColumns, sellerssAvailableColumns));
        return descriptor;
    }

}

I've updated the Table class to include some utility methods.

public class TableDescriptor {
    private String tableName;
    private String columnId;
    private String[] availableColumns;

    public String getTableName() {
        return tableName;
    }

    public void setTableName(String tableName) {
        this.tableName = tableName;
    }

    public String getColumnId() {
        return columnId;
    }

    public void setColumnId(String columnId) {
        this.columnId = columnId;
    }

    public String[] getAvailableColumns() {
        return availableColumns;
    }

    public void setAvailableColumns(String[] availableColumns) {
        this.availableColumns = availableColumns;
    }
}

TableDescriptor is just a container class.

public final class HektorContentProviderContract {
    public static final String AUTHORITY = "fr.intuitiv.hektor.contentprovider";

    public static final String APPOINTMENT_WITH_TYPE_BASE_PATH = "appointment_with_type";
    public static final Uri APPOINTMENT_WITH_TYPE_CONTENT_URI = Uri.parse("content://" + AUTHORITY
            + "/" + APPOINTMENT_WITH_TYPE_BASE_PATH);
    public static final String APPOINTMENT_WITH_TYPE_CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
            + "/vnd." + AUTHORITY + "." + APPOINTMENT_WITH_TYPE_BASE_PATH;
    public static final String APPOINTMENT_WITH_TYPE_CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
            + "/vnd." + AUTHORITY + "." + APPOINTMENT_WITH_TYPE_BASE_PATH;
}

I usually create some "Contract" classes to store any public constants.

public class ContentProviderHelper {
    private Context context;

    public Context getContext() {
        return context;
    }

    public void setContext(Context context) {
        this.context = context;
    }

    public ContentProviderHelper(Context context) {
        this.setContext(context);
    }

    public Cursor query(SQLiteOpenHelper database, TableDescriptor table,
            boolean isSingular, Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {

        // Uisng SQLiteQueryBuilder instead of query() method
        SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();

        // Set the table
        queryBuilder.setTables(table.getTableName());

        if (isSingular) {
            queryBuilder.appendWhere(table.getColumnId() + "="
                    + uri.getLastPathSegment());
        }

        SQLiteDatabase db = database.getWritableDatabase();
        Cursor cursor = queryBuilder.query(db, projection, selection,
                selectionArgs, null, null, sortOrder);
        // Make sure that potential listeners are getting notified
        cursor.setNotificationUri(getContext().getContentResolver(), uri);

        return cursor;
    }

    public Uri insert(SQLiteOpenHelper database, TableDescriptor table, Uri uri, ContentValues values) {
        SQLiteDatabase sqlDB = database.getWritableDatabase();
        long id = 0;
        id = sqlDB.insertWithOnConflict(table.getTableName(), null, values, SQLiteDatabase.CONFLICT_REPLACE);
        getContext().getContentResolver().notifyChange(uri, null);
        return Uri.withAppendedPath(getTableUri(table), Long.toString(id));
    }

    public int delete(SQLiteOpenHelper database, TableDescriptor table, boolean isSingular, Uri uri, String selection, String[] selectionArgs) {
        int rowsDeleted = 0;
        SQLiteDatabase sqlDB = database.getWritableDatabase();
        if (!isSingular) {
            rowsDeleted = sqlDB.delete(table.getTableName(), selection,
                    selectionArgs);
        } else {
            String id = uri.getLastPathSegment();
            if (TextUtils.isEmpty(selection)) {
                rowsDeleted = sqlDB.delete(table.getTableName(),
                        table.getColumnId() + "=" + id, null);
            } else {
                rowsDeleted = sqlDB.delete(table.getTableName(),
                        table.getColumnId() + "=" + id + " and " + selection,
                        selectionArgs);
            }
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return rowsDeleted;
    }

    public int update(SQLiteOpenHelper database, TableDescriptor table, boolean isSingular, Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {

        SQLiteDatabase sqlDB = database.getWritableDatabase();
        int rowsUpdated = 0;
        if (!isSingular) {
            rowsUpdated = sqlDB.update(table.getTableName(), values, selection,
                    selectionArgs);
        } else {
            String id = uri.getLastPathSegment();
            if (TextUtils.isEmpty(selection)) {
                rowsUpdated = sqlDB.update(table.getTableName(), values,
                        table.getColumnId() + "=" + id, null);
            } else {
                rowsUpdated = sqlDB.update(table.getTableName(), values,
                        table.getColumnId() + "=" + id + " and " + selection,
                        selectionArgs);
            }
        }
        getContext().getContentResolver().notifyChange(uri, null);
        return rowsUpdated;
    }

    public void checkColumns(TableDescriptor table, String[] projection) {
        String[] available = table.getAvailableColumns();
        if (projection != null) {
            HashSet<String> requestedColumns = new HashSet<String>(
                    Arrays.asList(projection));
            HashSet<String> availableColumns = new HashSet<String>(
                    Arrays.asList(available));
            // Check if all columns which are requested are available
            if (!availableColumns.containsAll(requestedColumns)) {
                throw new IllegalArgumentException(
                        "Unknown columns in projection");
            }
        }
    }

    protected Uri getTableUri(TableDescriptor table) {
        Uri result = null;

        String tableName = table.getTableName();
        if (AppointmentWithTypeView.TABLE_NAME.equals(tableName)) {
            result = HektorContentProviderContract.APPOINTMENT_WITH_TYPE_CONTENT_URI;
        }

        return result;
    }
}

Here is another utility class I'm using when working with ContentProviders. It simplifies the database management operations. It is quite handy if you have a lot of tables to manage.

public class UriDescriptor {
    private TableDescriptor table;
    private boolean singular;

    public boolean isSingular() {
        return singular;
    }

    public void setSingular(boolean singular) {
        this.singular = singular;
    }

    public TableDescriptor getTable() {
        return table;
    }

    public void setTable(TableDescriptor table) {
        this.table = table;
    }
}

This is again just a container class - not really interesting.

public class HektorContentProvider extends ContentProvider {

    private ContentProviderHelper helper;

    // database
    private HektorDatabaseHelper database;

    // Used for the UriMacher
    private static final int APPOINTMENT_WITH_TYPE = 290;
    private static final int APPOINTMENT_WITH_TYPE_ID = 300;

    private static final UriMatcher sURIMatcher = new UriMatcher(
            UriMatcher.NO_MATCH);
    static {
        sURIMatcher.addURI(HektorContentProviderContract.AUTHORITY,
                HektorContentProviderContract.APPOINTMENT_WITH_TYPE_BASE_PATH, APPOINTMENT_WITH_TYPE);
        sURIMatcher.addURI(HektorContentProviderContract.AUTHORITY,
                HektorContentProviderContract.APPOINTMENT_WITH_TYPE_BASE_PATH + "/#",
                APPOINTMENT_WITH_TYPE_ID);
    }

    @Override
    public boolean onCreate() {
        database = new HektorDatabaseHelper(getContext());
        helper = new ContentProviderHelper(getContext());
        return false;
    }

    @Override
    public Cursor query(Uri uri, String[] projection, String selection,
            String[] selectionArgs, String sortOrder) {

        UriDescriptor descriptor = getDescriptor(uri);
        helper.checkColumns(descriptor.getTable(), projection);

        Cursor cursor = helper.query(database, descriptor.getTable(),
                descriptor.isSingular(), uri, projection, selection,
                selectionArgs, sortOrder);

        return cursor;
    }

    @Override
    public String getType(Uri uri) {
        return null;
    }

    @Override
    public Uri insert(Uri uri, ContentValues values) {
        UriDescriptor descriptor = getDescriptor(uri);
        Uri result = helper
                .insert(database, descriptor.getTable(), uri, values);
        return result;
    }

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) {
        int rowsDeleted = 0;
        UriDescriptor descriptor = getDescriptor(uri);
        rowsDeleted = helper.delete(database, descriptor.getTable(),
                descriptor.isSingular(), uri, selection, selectionArgs);
        return rowsDeleted;
    }

    @Override
    public int update(Uri uri, ContentValues values, String selection,
            String[] selectionArgs) {

        UriDescriptor descriptor = getDescriptor(uri);
        int rowsUpdated = helper.update(database, descriptor.getTable(),
                descriptor.isSingular(), uri, values, selection, selectionArgs);
        return rowsUpdated;
    }

    protected UriDescriptor getDescriptor(Uri uri) {
        UriDescriptor descriptor = new UriDescriptor();

        int uriType = sURIMatcher.match(uri);
        switch (uriType) {
        case APPOINTMENT_WITH_TYPE:
            descriptor.setSingular(false);
            descriptor.setTable(AppointmentWithTypeView.getDescriptor());
            break;
        case APPOINTMENT_WITH_TYPE_ID:
            descriptor.setSingular(true);
            descriptor.setTable(AppointmentWithTypeView.getDescriptor());
            break;
        default:
            throw new IllegalArgumentException("Unknown URI: " + uri);
        }

        return descriptor;
    }

}

That's the ContentProvider class. It is quite simple, since most of the work is done in the ContentProviderHelper class.

This is a class representing a View. I write such classes for each View or Table I want to create in my database. Both entities are pretty much used the same way - you just have to change the SQL statement from CREATE VIEW to CREATE TABLE. As you can see, views can be created based on a SELECT statement - so they are very useful if you want to join several tables. When inserting to any of the AppointmentsTable / AppointmentTypesTable tables the data would be available via the View too. So I prefer to create such View and a ContentProvider working over it. SELECT queries are simple (just read from the View). You would have to handle INSERT / DELETES differently though - i.e. insert the data to the associated table.

My application loads its data from web service, so I'm doing it on the background. I've create CONTENT_URL in the ContentProvider for each table (i.e. AppointmentsTable and AppointmentTypesTable). Those are used by the background process to insert / update the data. The UI uses only the CONTENT_URLs connected to the Views, since they just need to read the data.

Let me know if you've got the idea. I could share some more code if needed. :)

Samuil Yanovski
  • 2,837
  • 17
  • 19
  • wow, this is amazing..so from what i can gather,i can have a single database helper class with as many classes as i need to have for each view?then what would the content provider class look like?with my example for instance,i have 3 tables.And what does this mean '.*' in the query.I've never seen them being used together – naffie Mar 21 '14 at 10:43
  • Yup, that's the idea more or less - you have a single helper class and multiple table / view classes (i.e. one such file for each table / view). table_name.* means every_column_from_that_table. It is just shorter that writing every column separately. :) SimpleCursorAdapter works the same with View / Table. You just make a query over the View's CONTENT_URL and give the Cursor to the Adapter. I'll edit the answer to add some code about the ContentProvider - would need a few hours to do it though, since I got to go for now. :) – Samuil Yanovski Mar 21 '14 at 11:06
  • Okay..thank you so m uch for the help already...i'll be waiting for the feedback then – naffie Mar 21 '14 at 11:41
  • @naffie, please check the new classes. They should be enough to get you going. :) If you have any troubles figuring out what's happening, don't hesitate to ask. – Samuil Yanovski Mar 21 '14 at 18:19
  • after making the changes above in my code, now i get the following error on on logcat 03-24 10:55:12.427: E/SQLiteLog(1364): (1) ambiguous column name: _id – naffie Mar 24 '14 at 07:58
  • 1
    That's caused by an SQL error. All your tables have "_id" column. Thus you can't just say "SELECT VIEW table_a.*, table_b.*", because this would include two "_id" columns and the database can not figure out which of the columns you really want (this is why _id is ambiguous - there are multiple columns with this name). Check my example - I've included all columns from the main table only (AppointmentTypesTable). From the other tables I take only specific columns (I've not included the _id column from the buyer / seller tables). I guess something similar would work for you too. :) – Samuil Yanovski Mar 24 '14 at 11:06
  • hmmm..i would never have guessed that!.quick question,yes i do have the column _id in all my three tables and i use them when creating other tables or views because some PK act as fk in others-my meters table displays a list of meters in the db.Then each customer/household has their own meter-so the meter_id(_id) is a foreign key in the customers table i.e KEY_METER_ID.Then when printing each customer's bill,the waterbills table,there is a field for the meter_id,so i can link each customer's bill back to them using their meter_id.So KEY_METER_ID in customers is foreign key on this table again. – naffie Mar 24 '14 at 11:24
  • Would i still do it the same way as you even after having having foreign keys with the implementation i've told you about?Or might you know a better way of designing my app? – naffie Mar 24 '14 at 11:30
  • I'm posting an edit in my question to show you one of my view,ViewCustomers which selects just a few details from customers table,not everything,and seeks to display their meter number and plot number as well – naffie Mar 24 '14 at 11:35
  • 1
    So you have: bills which refer to customer which refers to meters (i.e. bill.customer_id = customer.id and customer.meter_id = meter.id). Is this correct? If this is the case - it is perfectly fine, there is nothing to improve. :) According to the Android guidelines you should have _id column in every table - don't worry about that. Just make you to either rename those column in the SELECT statement (using the "AS" keyword) or just strip them from the statement (you could take the foreign key columns instead since they are equal according the your WHERE clause). – Samuil Yanovski Mar 24 '14 at 12:14
  • Yes,that is correct..thank you..but what do these " || ' ' || " mean in your query?What difference will it make if i just write mine as i have done in the next edit on my question? – naffie Mar 24 '14 at 12:36
  • || concatenates two strings. In my case I want to concatenated the user's title (i.e. Mr) with a white space (' ') and then his last name . I just want to generate the full name in a single column - you could achieve the same result with String manipulation in Java code. :) – Samuil Yanovski Mar 24 '14 at 13:47
  • it works!! :-)) than you so much Samuil.you're the best..after many days finally i got a solution.I alreadt marked this as the answer.Another quick question though,you said that you're downloading your data from a webservice?are you using the sync adapter? – naffie Mar 25 '14 at 06:42
  • Nope, I'm not :) Actually I've never worked with the Sync adapter. I just a an IntentService, which handles different download request and distribute the data via Otto's messages (https://github.com/square/otto) or just letting the Cursors handling the dataChanged event. :) I find Otto pretty easy to use - it is somewhat more straightforward since you don't have to handle leaked / closed cursors and stuff. :) – Samuil Yanovski Mar 25 '14 at 07:45
  • oh, okay.That sounds interesting.i am downloading data from a webservice too and was trying to implement the sync adapter.I guess i'll do more research on that.One quick question though,when i read the meters from the meters table,everything works fine..however,ii have a view for just the customer's last name from customer's table and their meter and plot number from the meters table.These two are foreign keys in the customers table.This view gives me a null pointer exception.So what is the best way to display this view..i'm posting my sample code in another edit. – naffie Mar 25 '14 at 08:00
  • Just a quick question - can you show which in row 48 (the one with the exception). I don't know how long is your import section, so can't figure it out by myself. :) Just another note - query the database could be a long process, it is usually preferred to do it via CursorLoaders to avoid blocking the main thread (this is not related to the null pointer, just saying). :) – Samuil Yanovski Mar 25 '14 at 08:26
  • I do it via cursor loaders..the two classes are somewhat long,that's why i just picked some parts of it..i will make the correction..sorry about that – naffie Mar 25 '14 at 08:30
  • i solved it..very silly mistake.i have different layouts for different screen sizes.so i had made some changes in some files and forgot others.That is why findViewById was returning null. – naffie Mar 25 '14 at 09:51
  • oh, glad you've solved it. :) If you have any other questions - don't hesitate to drop me a message. – Samuil Yanovski Mar 25 '14 at 16:05
  • Thank you so much..you have been of great help :-)) – naffie Mar 26 '14 at 07:14
  • let me apologize for asking some questions which are pretty basic.I'm still very new to android but i love all that i'm learning.I have another question,can i show you which one it is from my profile or just go ahead and ask it here?It's been there for a while..and i haven't been able to solve that – naffie Mar 26 '14 at 11:04
  • Just drop me the link to the other question. :) I think it would be better if I take a look at the thread rather than using the comment section only. :) – Samuil Yanovski Mar 26 '14 at 14:20
  • okay.. (http://stackoverflow.com/questions/22406718/getdhcpinfo-obtaining-wrong-ip-address-in-android) – naffie Mar 26 '14 at 14:32