I am currently working on an Android Studio application that utilizes SQLite database that can store customer records of a car purchase, and the app stops as soon as the ADD button is clicked. Apparently, there is a null object reference. The app has a MainActivity that has an add button to allow the user the add customer database entries. When clicked the app should go to the CustomerRcrdActivity to allow database entries, from there the user should be able to add, view, update, or delete entries all why the entry should display on the screen.
I am learning how to create apps for Android, so please be kind. I am not asking for you to do anything for me, but please kindly point me in the correct direction. I have searched everywhere for input but fear I am overlooking a logic error, I could use a second set of eyes.
Any input or tips on the matter would be much appreciated.
Thank you very much,
Below is the code I suspect could be at fault, (I will include the XML just in case I have done something wonky).
content_main.xml (it includes the listview I would like to update upon adding an entry) If there is a simpler way, I am not set in stone.
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".MainActivity"
tools:showIn="@layout/activity_main">
<TextView
android:id="@+id/ContentMain_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="sans-serif-smallcaps"
android:text="@string/content_main_title"
android:textAlignment="center"
android:textSize="20sp"
android:textStyle="bold"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.027" />
<Button
android:id="@+id/button_CR_add"
android:layout_width="112dp"
android:layout_height="wrap_content"
android:layout_marginBottom="416dp"
android:layout_marginEnd="8dp"
android:layout_marginTop="7dp"
android:onClick="buttonClicked"
android:text="@string/ContentMain_button_CD_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.054"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/ContentMain_title"
app:layout_constraintVertical_bias="0.022" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="486dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button_CR_add">
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
</android.support.constraint.ConstraintLayout>
The XML for the CustomerRcrdActivity is ommited
CustomerRcrdActivity.java
public class CustomerRcrdActivity extends AppCompatActivity
{
private EditText FName, LName, CarMake, CarModel, CarCost;
private String F_Name;
private String L_Name;
private String Car_Make;
private String Car_Model;
private double d_Car_Cost;
private DatabaseHandler dh;
private dataAdapter da;
private Customer dataModel;
private ListView lv;
private long mId;
//<<<<<<<<<< new class variables
private String original_FName, original_LName, original_CarMake, original_CarModel, original_CarCost;
private boolean mLoaded = false;
private Button mUpdateButton;
private Button mAddButton;
private Button mDeleteButton;
private ArrayList<Customer> mCustomers = new ArrayList<>();
private List<EditText> mAlleditTexts = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_customer_rcrd);
// GET Intent THAT STARTED ACTIVITY
Intent intent = getIntent();
// INSTANTIATE DATABASE HANDLER
dh = new DatabaseHandler(this);
FName = findViewById(R.id.editText2_FName);
LName = findViewById(R.id.editText2_LName);
CarMake = findViewById(R.id.editText2_CarMake);
CarModel = findViewById(R.id.editText2_CarModel);
CarCost = findViewById(R.id.editText2_CarCost);
lv = findViewById(R.id.listView);
//<<<<<<<<<<< ADDED
mAlleditTexts.add(FName);
mAlleditTexts.add(LName);
mAlleditTexts.add(CarMake);
mAlleditTexts.add(CarModel);
mAlleditTexts.add(CarCost);
//<<<<<<<<<< END OF ADDED
Button mViewButton = findViewById(R.id.button_CR_view);
mAddButton = findViewById(R.id.button_CR_add);
mUpdateButton = findViewById(R.id.button_CR_update);
mDeleteButton = findViewById(R.id.button_CR_delete);
mViewButton.setVisibility(View.GONE); // never Show and free screen space
mViewButton.setText(R.string.mViewButton_setText); // Hijack View Button for clear data
mViewButton.setVisibility(View.VISIBLE); // Show the View (now CLEAR)
mAddButton.setEnabled(false); // Can't click Add as nothing to add
mUpdateButton.setEnabled(false); // Can't click Update nothing to update
mDeleteButton.setEnabled(false); // Can't click Delete as nothing to delete
setOriginalValues();
ShowRecords(); //<<<< Always show the list no need for View button
setEditTextFocusChangedListener(mAlleditTexts);
}
//<<<<<<<<<< NEW METHOD
private void setEditTextFocusChangedListener(List<EditText> edit_texts)
{
for (EditText e: edit_texts)
{
e.setOnFocusChangeListener(new View.OnFocusChangeListener()
{
@Override
public void onFocusChange(View view, boolean b)
{
if (areOriginalValuesChanged())
{
if (mLoaded)
{
mUpdateButton.setEnabled(true);
mDeleteButton.setEnabled(false);
mAddButton.setEnabled(false);
} else
{
}
} else
{
if (!mLoaded)
{
mAddButton.setEnabled(true);
} else
{
mAddButton.setEnabled(false);
}
}
}
});
}
}
//<<<<<<<<<< NEW METHOD
private void setOriginalValues()
{
original_FName = FName.getText().toString();
original_LName = LName.getText().toString();
original_CarMake = CarMake.getText().toString();
original_CarModel = CarModel.getText().toString();
original_CarCost = CarCost.getText().toString();
}
//<<<<<<<<<< NEW METHOD
private boolean areOriginalValuesChanged()
{
/*if (original_FName.equals(FName.getText().toString())
&& original_LName.equals(LName.getText().toString())
&& original_CarMake.equals(CarMake.getText().toString())
&& original_CarModel.equals(CarModel.getText().toString())
&& original_CarCost.equals(CarCost.getText().toString())
)
{
return false;
}
return true;*/
return !original_FName.equals(FName.getText().toString())
|| !original_LName.equals(LName.getText().toString())
|| !original_CarMake.equals(CarMake.getText().toString())
|| !original_CarModel.equals(CarModel.getText().toString())
|| !original_CarCost.equals(CarCost.getText().toString());
}
//<<<<<<<<<< NEW METHOD
private void clearEditTexts(List<EditText> editTexts)
{
for (EditText e: editTexts)
{
e.setText("");
}
setOriginalValues();
mLoaded = false;
}
public void buttonClicked(View view)
{
int id = view.getId();
switch (id)
{
case R.id.button_CR_update:
// CALL updateCustomer() TO UPDATE CUSTOMER RECORDS
updateCustomer();
mLoaded = false;
break;
case R.id.button_CR_add:
// CALL addCustomer() TO ADD TO DATABASE
addCustomer();
// CLEAR FIELDS
clearEditTexts(mAlleditTexts);
mLoaded = false;
break;
case R.id.button_CR_delete:
// CALL deleteCustomer() TO DELETE CUSTOMER RECORD
deleteCustomer();
break;
case R.id.button_CR_view:
// CLEAR FIELDS
clearEditTexts(mAlleditTexts);
mLoaded = false;
mAddButton.setEnabled(true);
mUpdateButton.setEnabled(false);
mDeleteButton.setEnabled(false);
break;
}
// UPDATE LIST IRRESPECTIVE OF BUTTON CLICKED
ShowRecords();
}
// UPDATE CUSTOMER RECORD
private void updateCustomer()
{
getValues();
if (dh.updateCustomer(dataModel, mId)>0)
{
Toast.makeText(getApplicationContext(), "Customer Updated Successfully", Toast.LENGTH_LONG).show();
} else
{
Toast.makeText(getApplicationContext(), "Customer Not Updated", Toast.LENGTH_LONG).show();
}
}
// INSERT DATA INTO DATABASE
private void addCustomer()
{
boolean ok_to_add = true;
// GET VALUES FROM EditText
getValues();
if (F_Name == null || F_Name.length() < 1) ok_to_add = false;
if (L_Name == null || L_Name.length() < 1) ok_to_add = false;
if (Car_Make == null || Car_Make.length() < 1) ok_to_add = false;
if (Car_Model == null || Car_Model.length() < 1) ok_to_add = false;
if (ok_to_add)
{
dh.addCustomers(new Customer(F_Name, L_Name, Car_Make, Car_Model, d_Car_Cost));
Toast.makeText(getApplicationContext(), "Customer Added Successfully", Toast.LENGTH_LONG).show();
} else
{
Toast.makeText(getApplicationContext(), "Unable To Add - Some Data hasn't been given", Toast.LENGTH_LONG).show();
}
}
// DELETE CUSTOMER RECORD
private void deleteCustomer()
{
getValues();
if (dh.deleteCustomer(mId))
{
Toast.makeText(getApplicationContext(), "Customer Deleted Successfully", Toast.LENGTH_LONG).show();
} else
{
Toast.makeText(getApplicationContext(), "Customer Not Deleted", Toast.LENGTH_LONG).show();
}
}
// FUNCTION TO GET VALUES FROM EditText
private void getValues()
{
F_Name = FName.getText().toString();
L_Name = LName.getText().toString();
Car_Make = CarMake.getText().toString();
Car_Model = CarModel.getText().toString();
String car_Cost = CarCost.getText().toString();
if (car_Cost.length() < 1)
{
car_Cost = "0.00";
}
d_Car_Cost = Double.parseDouble(car_Cost);
}
// RETRIEVE DATA FROM DATABASE & SET TO LIST VIEW
//<<<<<<<<<< CHANGED QUITE A BIT
// Introduced single Customer List with class scope rather than create new list and adapter every time
// i.e. mCustomers
// Always clear mCustomers and rebuild from database
// if da (the adapter) is null and therefore hasn't been instantiated, instantiate it just once
// otherwise always notify the adapter that the data (mCustomer) has changed
// Added code to the Listener (added just the once now) to set the edit text's to the values
// of the clicked item in the list (load the data)
// setting flag to say that the data has just been loaded.
// also setting the original values to the new data
private void ShowRecords()
{
mCustomers.clear();
mCustomers.addAll(dh.getAllCustomers());
if (da == null)
{
da = new dataAdapter(this, mCustomers);
lv.setAdapter(da);
lv.setOnItemClickListener(new AdapterView.OnItemClickListener()
{
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
dataModel = mCustomers.get(position);
//<<<<<<<<<< Added
FName.setText(dataModel.getFName());
LName.setText(dataModel.getLName());
CarMake.setText(dataModel.get_CarMake());
CarModel.setText(dataModel.get_CarModel());
CarCost.setText(String.valueOf(dataModel.get_CarCost()));
mLoaded = true;
setOriginalValues();
mDeleteButton.setEnabled(true);
mUpdateButton.setEnabled(false);
mAddButton.setEnabled(false);
mId = dataModel.getID();
//<<<<<<<<<< End of Added
Toast.makeText(getApplicationContext(), String.valueOf(dataModel.getID()), Toast.LENGTH_SHORT).show();
}
});
} else
{
da.notifyDataSetChanged();
}
}
}
DatabaseHandler.java
public class DatabaseHandler extends SQLiteOpenHelper
{
// DATABASE VERSION
private static final int DATABASE_VERSION = 1;
// DATABASE NAME
private static final String DATABASE_NAME = "CARDEALER.db";
// CUSTOMER TABLE NAME
public static final String TABLE_CUSTOMERS = "Customers";
// CUSTOMER TABLE COLUMN NAMES
private static final String KEY_ID = "ID";
private static final String KEY_FNAME = "First";
private static final String KEY_LNAME = "Last";
private static final String KEY_CAR_MAKE = "Make";
private static final String KEY_CAR_MODEL = "Model";
private static final String KEY_CAR_COST = "Cost";
DatabaseHandler(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
// CREATE TABLES
@Override
public void onCreate(SQLiteDatabase dh)
{
String CREATE_TABLE_CUSTOMERS = "CREATE TABLE " + TABLE_CUSTOMERS + "("
+ KEY_ID +" INTEGER PRIMARY KEY,"
+ KEY_FNAME +" TEXT,"
+ KEY_LNAME +" TEXT,"
+ KEY_CAR_MAKE +" TEXT,"
+ KEY_CAR_MODEL +" TEXT,"
+ KEY_CAR_COST +" NUMERIC" + ")";
dh.execSQL(CREATE_TABLE_CUSTOMERS);
}
// UPGRADING DATABASE
@Override
public void onUpgrade(SQLiteDatabase dh, int oldVersion, int newVersion)
{
// DROP OLDER TABLE IF EXISTED
dh.execSQL("DROP TABLE IF EXISTS " + TABLE_CUSTOMERS);
// CREATE TABLES AGAIN
onCreate(dh);
}
/**
* All CRUD (CREATE, READ, UPDATE, DELETE) OPERATIONS
*/
// INSERT VALUES TO TABLE CUSTOMERS
public void addCustomers(Customer customer)
{
SQLiteDatabase dh = this.getReadableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_FNAME, customer.getFName());
values.put(KEY_LNAME, customer.getLName() );
values.put(KEY_CAR_MAKE, customer.get_CarMake());
values.put(KEY_CAR_MODEL, customer.get_CarModel());
values.put(KEY_CAR_COST, customer.get_CarCost());
dh.insert(TABLE_CUSTOMERS, null, values);
dh.close();
}
/**
*GETTING ALL CUSTOMERS
**/
public List<Customer> getAllCustomers()
{
List<Customer> customerList = new ArrayList<Customer>();
// SELECT ALL QUERY
String selectQuery = "SELECT * FROM " + TABLE_CUSTOMERS;
SQLiteDatabase dh = this.getWritableDatabase();
Cursor cursor = dh.rawQuery(selectQuery, null);
// LOOP THROUGH ALL ROWS & ADD TO LIST
if (cursor.moveToFirst())
{
do {
Customer customer = new Customer();
customer.setID(Integer.parseInt(cursor.getString(0)));
customer.setFName(cursor.getString(1));
customer.setLName(cursor.getString(2));
customer.set_CarMake(cursor.getString(3));
customer.set_CarModel(cursor.getString(4));
customer.set_CarCost(cursor.getDouble(5));
// ADDING CUSTOMER TO LIST
customerList.add(customer);
} while (cursor.moveToNext());
}
// CLOSE OUT RAW QUERY CALL
cursor.close();
// RETURN CUSTOMER LIST
return customerList;
}
/**
*UPDATING SINGLE CUSTOMER
**/
public int updateCustomer(Customer customer, long id)
{
if (customer == null)
{
return 0;
}
SQLiteDatabase dh = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_FNAME, customer.getFName());
values.put(KEY_LNAME, customer.getLName());
values.put(KEY_CAR_MAKE, customer.get_CarMake());
values.put(KEY_CAR_MODEL, customer.get_CarModel());
values.put(KEY_CAR_COST, customer.get_CarCost());
// UPDATING ROW
return dh.update(TABLE_CUSTOMERS, values, KEY_ID + " = ?",
new String[] { String.valueOf(id) });
}
/**
*DELETING SINGLE CUSTOMER
**/
public boolean deleteCustomer(long Id)
{
boolean rv = false;
SQLiteDatabase dh = this.getWritableDatabase();
rv = (dh.delete(TABLE_CUSTOMERS, KEY_ID + " = ?",
new String[] { String.valueOf(Id) }) > 0);
dh.close();
return rv;
}
}
dataAdapter.java
public class dataAdapter extends ArrayAdapter<Customer>
{
public dataAdapter(Context context, ArrayList<Customer> customers)
{
super(context, R.layout.list_customers, customers);
Context context1 = context;
ArrayList<Customer> mcustomer = customers;
}
public class Holder
{
TextView idV;
TextView nameFV;
TextView nameLV;
TextView carmakeV;
TextView carmodelV;
TextView carcostV;
}
@NonNull
@Override
public View getView(int position, View convertView, @NonNull ViewGroup parent)
{
// GET DATA ITEM FOR THIS POSITION
Customer data = getItem(position);
// CHECK IF EXISTING VIEW IS BEING REUSED, OTHERWISE INFLATE VIEW
// VIEW LOOKUP CACHE STORED IN TAG
Holder viewHolder;
if (convertView == null)
{
viewHolder = new Holder();
LayoutInflater inflater = LayoutInflater.from(getContext());
convertView = inflater.inflate(R.layout.list_customers, parent, false);
viewHolder.idV = (TextView) convertView.findViewById(R.id.textView3_CustomerID);
viewHolder.nameFV = (TextView) convertView.findViewById(R.id.textView3_FName);
viewHolder.nameLV = (TextView) convertView.findViewById(R.id.textView3_LName);
viewHolder.carmakeV = (TextView) convertView.findViewById(R.id.textView3_CarMake);
viewHolder.carmodelV = (TextView) convertView.findViewById(R.id.textView3_CarModel);
viewHolder.carcostV = (TextView) convertView.findViewById(R.id.textView3_CarCost);
convertView.setTag(viewHolder);
} else
{
viewHolder = (Holder) convertView.getTag();
}
viewHolder.nameFV.setText("First Name: " + data.getFName());
viewHolder.nameLV.setText("Last Name: " + data.getLName());
viewHolder.carmakeV.setText("Car Make: " + data.get_CarMake());
viewHolder.carmodelV.setText("Car Model: " + data.get_CarModel());
viewHolder.carcostV.setText("Car Cost: " + data.get_CarCost());
// RETURN COMPLETED VIEW TO RENDER ON SCREEN
return convertView;
}
}
I am including MainActivity in case I have missed something there. Here is MainActivity.java
public class MainActivity extends AppCompatActivity
{
//public ListView lv;
@Override
protected void onCreate(Bundle savedInstanceState)
{
// even tried ListView here (R.id.listView....)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
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, "Please, click ADD button to begin! ", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
});
}
@Override
public boolean onCreateOptionsMenu(Menu menu)
{
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item)
{
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_about)
{
// possibly go to another page
Toast.makeText(MainActivity.this,
"SQLite Database App", Toast.LENGTH_LONG).show();
return true;
}
return super.onOptionsItemSelected(item);
}
public void buttonClicked(View view)
{
// START NEW ACTIVITY WHEN ADD BUTTON CLICKED
Intent intent = new Intent(this, CustomerRcrdActivity.class);
startActivity(intent);
}
}
Here is the Logcat log:
05-30 09:02:03.253 30145-30145/edu.phoenix.mbl402.week4apppp1943_rev1 E/AndroidRuntime: FATAL EXCEPTION: main
Process: edu.phoenix.mbl402.week4apppp1943_rev1, PID: 30145
java.lang.RuntimeException: Unable to start activity ComponentInfo{edu.phoenix.mbl402.week4apppp1943_rev1/edu.phoenix.mbl402.week4apppp1943_rev1.CustomerRcrdActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.widget.ListAdapter)' on a null object reference
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2817)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'void android.widget.ListView.setAdapter(android.widget.ListAdapter)' on a null object reference
at edu.phoenix.mbl402.week4apppp1943_rev1.CustomerRcrdActivity.ShowRecords(CustomerRcrdActivity.java:276)
at edu.phoenix.mbl402.week4apppp1943_rev1.CustomerRcrdActivity.onCreate(CustomerRcrdActivity.java:79)
at android.app.Activity.performCreate(Activity.java:6975)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1213)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2770)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2892)
at android.app.ActivityThread.-wrap11(Unknown Source:0)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1593)
at android.os.Handler.dispatchMessage(Handler.java:105)
at android.os.Looper.loop(Looper.java:164)
at android.app.ActivityThread.main(ActivityThread.java:6541)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
Also, I have included the sugested modifications, thank you again!