Estoy tratando de editar un objeto que se muestra en una ListView y cuyos datos están guardados en Firebase. El objeto es de tipo Medicina y cuenta con dos propiedades de tipo String: nombre y dosis.
Actualmente consigo eliminar los elementos de la listview, y a la hora de editarlos conseguir que se muestren los valores de sus parámetros antiguos. Sin embargo, a la hora de guardar los nuevos valores para sus parámetros dosis y nombre, se produce el error que he indicado en el título de esta pregunta.
Attempt to invoke interface method 'java.lang.Object java.util.List.set(int, java.lang.Object)' on a null object reference
El código que estoy utilizando es el siguiente:
Layouts:
A) item_medicina_xml.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/medicamentoNombre"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:textAppearance="?android:attr/textAppearanceMedium"
tools:text="nombre"
/>
<TextView
android:id="@+id/medicamentoDosis"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:textAppearance="?android:attr/textAppearanceSmall"
tools:text="dosis"
/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/iconoEditMedicina"
android:src="@drawable/ic_edit"
android:background="@drawable/bg_round"
android:layout_marginRight="30dp">
</ImageButton>
<ImageButton
android:layout_width="40dp"
android:layout_height="40dp"
android:id="@+id/iconoDeleteMedicina"
android:src="@drawable/ic_delete"
android:background="@drawable/bg_round"
android:layout_toLeftOf="@id/iconoEditMedicina">
</ImageButton>
</LinearLayout>
</LinearLayout>
B) input_box_medicine_edit.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/txtmessage"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="10dp"
android:textSize="16sp"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center_horizontal"
>
<EditText
android:id="@+id/txtMediNameInput"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_weight="2"
/>
<EditText
android:id="@+id/txtMediDosisInput"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_weight="2"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
/>
<Button
android:id="@+id/btMediAct"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Actualizar"
android:layout_weight="1"
/>
</LinearLayout>
</LinearLayout>
C) activity_modified_medicine.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center"
android:padding="16dp"
>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:inputType="textPersonName"
android:ems="10"
android:layout_marginTop="20dp"
android:id="@+id/add_new_medicine_modified"
android:hint="Escribe el nombre del medicamento"
></EditText>
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAlignment="center"
android:inputType="text"
android:ems="10"
android:layout_marginTop="20dp"
android:id="@+id/add_new_dosis_medicine"
android:hint="Escribe su dosis"
></EditText>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Añadir medicamento"
android:id="@+id/btnAñadirMedicinaModified"></Button>
<ListView
android:id="@+id/listMedicines"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="20dp"/>
</LinearLayout>
</RelativeLayout>
Y el código de la activity AddMedicineModified.java:
public class AddMedicineModified extends AppCompatActivity {
private static final String TAG = "AddNewMedicine";
//declaro las variables
private Button mAddToDB;
private EditText mnombre;
private EditText mdosis;
//variables relacionadas con Firebase: para guardar los datos asociados al determinado usuario logueado, etc
private FirebaseDatabase mFirebaseDatabase;
private FirebaseAuth mAuth;
private FirebaseAuth.AuthStateListener mAuthListener;
private DatabaseReference myRef;
private DatabaseReference puntoDeAcceso;
private String userID;
//Para que se vean los datos de firebase del usuario
//
private List<Medicina> medicinas;
private List<String> list;
private MedicinaAdapter medicinaAdapter;
private ListView mListView;
private ChildEventListener childEventListener;
//Declarar las imágenes ImageView de delete y edit
private ImageButton mEditView;
private ImageButton mDeleteView;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_modified_medicine);
mListView = (ListView) findViewById(R.id.listMedicines);
//MedicinaAdapter con layout creado y lista vacía
final List<Medicina> medicinas = new ArrayList<>();
medicinaAdapter = new MedicinaAdapter(this, R.layout.item_medicina, medicinas);
mListView.setAdapter(medicinaAdapter);
mAddToDB=(Button) findViewById(R.id.btnAñadirMedicinaModified);
mnombre=(EditText) findViewById(R.id.add_new_medicine_modified);
mdosis=(EditText) findViewById(R.id.add_new_dosis_medicine);
//Firebase
mAuth = FirebaseAuth.getInstance();
mFirebaseDatabase = FirebaseDatabase.getInstance();
myRef = mFirebaseDatabase.getReference();
myRef=myRef.child("Medicamentos");
FirebaseUser user = mAuth.getCurrentUser();
userID = user.getUid();
//puntoDeAcceso=mFirebaseDatabase.getReference().child("Medicamentos").child(userID);
puntoDeAcceso=mFirebaseDatabase.getReference().child(userID).child("Medicamentos");
//esto para ver si está bien logeado
mAuthListener = new FirebaseAuth.AuthStateListener() {
@Override
public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if (user != null) {
// User is signed in
Log.d(TAG, "onAuthStateChanged:signed_in:" + user.getUid());
toastMessage("Has iniciado sesión con: " + user.getEmail());
} else {
// User is signed out
Log.d(TAG, "onAuthStateChanged:signed_out");
toastMessage("Has cerrado bien sesión.");
}
// ...
}
};
mAddToDB.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String newMedicine= mnombre.getText().toString();
String newDosis=mdosis.getText().toString();
Medicina medicine=new Medicina(newMedicine,newDosis);
puntoDeAcceso.push().setValue(medicine);
toastMessage("Añadiendo " + newMedicine + " a la base de datos...");
}
});
puntoDeAcceso.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
if (s != null) {
Log.d(TAG, s);
}
//Creo un objeto mensaje
Medicina mensaje = dataSnapshot.getValue(Medicina.class);
//y se lo añado al adapter
medicinaAdapter.add(mensaje);
}
@Override
public void onChildChanged(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
}
@Override
public void onChildRemoved(@NonNull final DataSnapshot dataSnapshot) {
//voy a intentar hacer el delete aquí
//ImageButton mDeleteView=(ImageButton) findViewById(R.id.iconoDeleteMedicina);
}
@Override
public void onChildMoved(@NonNull DataSnapshot dataSnapshot, @Nullable String s) {
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
}
public void showInputBox(Medicina oldItem, final int index){
final Dialog dialog=new Dialog(AddMedicineModified.this);
dialog.setTitle("Input Box");
dialog.setContentView(R.layout.input_box_medicine_edit);
TextView txtMessage=(TextView)dialog.findViewById(R.id.txtmessage);
txtMessage.setText("Edite el objeto");
txtMessage.setTextColor(Color.parseColor("#ff2222"));
//esto es para que salga un mensaje que indique al usuario qué tiene que hacer
//esto para que recupere los valores antiguos
EditText editTextName=(EditText)dialog.findViewById(R.id.txtMediNameInput);
EditText editTextDosis=(EditText)dialog.findViewById(R.id.txtMediDosisInput);
editTextName.setText(oldItem.getNombre());
editTextDosis.setText(oldItem.getDosis());
final Medicina updatedMedicina=new Medicina(editTextName.getText().toString(),editTextDosis.getText().toString());
final DatabaseReference db = FirebaseDatabase.getInstance().getReference().child(userID).child("Medicamentos");
Button bt=(Button)dialog.findViewById(R.id.btMediAct);
bt.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
medicinas.set(index,updatedMedicina);//error aquí
db.child("Nombre").setValue(updatedMedicina.getNombre().toString());
db.child("Dosis").setValue(updatedMedicina.getDosis().toString());
medicinaAdapter.notifyDataSetChanged();
dialog.dismiss();
}
});
dialog.show();
}
class MedicinaAdapter extends ArrayAdapter<Medicina> {
Context context;
List<Medicina> medicinas;
public MedicinaAdapter(@NonNull Context context, int resource,
@NonNull List<Medicina> medicinas) {
super(context, resource, medicinas);
this.context=context;
this.medicinas=medicinas;
}
public int getCount(){
return medicinas.size();
}
public Medicina getItem(int pos) {
return null;
}
public long getMedicinaId(int pos) {
return pos;
}
@Override
public View getView(final int i, View view, ViewGroup viewGroup) {
//Para el objeto i-ésimo recupero el elemento de la lista contenedora
Medicina medicina=medicinas.get(i);
//Recupero el "inflador" de layouts
LayoutInflater layoutInflater=getLayoutInflater().from(context);
//Inflo el layout item_message que he definido
view=layoutInflater.inflate(R.layout.item_medicina,viewGroup,false);
//Recupero los componentes del item_message
TextView nombre=view.findViewById(R.id.medicamentoNombre);
TextView dosis=view.findViewById(R.id.medicamentoDosis);
//Le asigno los valores del mensaje
nombre.setText(medicina.getNombre());
dosis.setText(medicina.getDosis());
//Inicializamos ImageViews
ImageButton mEditView;
mEditView=(ImageButton) view.findViewById(R.id.iconoEditMedicina);
ImageButton mDeleteView;
mDeleteView=(ImageButton) view.findViewById(R.id.iconoDeleteMedicina);
mEditView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showInputBox(medicinas.get(i),i);
}
});
mDeleteView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final Medicina medicinaBorrar=medicinas.get(i);
final DatabaseReference db = FirebaseDatabase.getInstance().getReference().child(userID).child("Medicamentos");
db.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
for(DataSnapshot snapshot: dataSnapshot.getChildren()){
Medicina medicamento=snapshot.getValue(Medicina.class);
if (medicinaBorrar.getNombre().equals(medicamento.getNombre())){
db.child(snapshot.getKey().toString()).removeValue();
medicinas.remove(i);
notifyDataSetChanged();
break;
}
}
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
}
});
/*medicinas.remove(i);
notifyDataSetChanged();*/ //esto funciona para quitar de listview pero no de firebase
}
});
//Devuelvo la vista que he creado
return view;
}
}
/**
* customizable toast
* @param message
*/
private void toastMessage(String message){
Toast.makeText(this,message,Toast.LENGTH_SHORT).show();
}
}
Y la clase Medicina.java:
public class Medicina {
String nombre;
String dosis;
//constructores
public Medicina(){
}
public Medicina(String nombre, String dosis){
this.nombre=nombre;
this.dosis=dosis;
}
public String getNombre(){
return nombre;
}
public void setNombre(String nombre){
this.nombre=nombre;
}
public String getDosis(){
return dosis;
}
public void setDosis(String dosis){
this.dosis=dosis;
}
}
¿Cómo puedo solucionar este fallo? No entiendo por qué se está produciendo, ¿Quizás debo inicializar antes el objeto updatedMedicina?
El stack del error es el siguiente:
E/AndroidRuntime: FATAL EXCEPTION: main Process: com.example.gstiandroidaplicacion, PID: 16591 java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.Object java.util.List.set(int, java.lang.Object)' on a null object reference at com.example.gstiandroidaplicacion.AddMedicineModified$4.onClick(AddMedicineModified.java:219) at android.view.View.performClick(View.java:6659) at android.view.View.performClickInternal(View.java:6631) at android.view.View.access$3100(View.java:790) at android.view.View$PerformClick.run(View.java:26187) at android.os.Handler.handleCallback(Handler.java:907) at android.os.Handler.dispatchMessage(Handler.java:105) at android.os.Looper.loop(Looper.java:216) at android.app.ActivityThread.main(ActivityThread.java:7625) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:524) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:987)