I am working at an Android app and got stuck with an unexplained effect of a button.
There are three activities that are involved in this: (you can find the full code on pastebin)
TripListActivity.java
//removed imports due to body limitation at 30000 chas
public class TripListActivity extends AppCompatActivity {
@BindView(R.id.rlvTrips)
RecyclerView rlvTrips;
private DatabaseReference databaseReference;
private FirebaseAuth firebaseAuth;
private FirebaseStorage firebaseStorage;
private List<Trip> recentTrips;
private List<Trip> pastTrips;
private List<StorageReference> imageRefsRecent;
private List<StorageReference> imageRefsPast;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//create instance of firebase auth
firebaseAuth = FirebaseAuth.getInstance();
//create instance of firebase storage
firebaseStorage = FirebaseStorage.getInstance();
//create instance of firebase database
databaseReference = FirebaseDatabase.getInstance().getReference();
recentTrips = new ArrayList<>();
pastTrips = new ArrayList<>();
imageRefsRecent = new ArrayList<>();
imageRefsPast = new ArrayList<>();
getAllTrips();
}
private void getAllTrips() {
final Date currentDate = new Date();
final long currentTime = currentDate.getTime();
databaseReference.child("users/" + firebaseAuth.getCurrentUser().getUid() + "/").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot dataSnapshot) {
DataSnapshot tripsDataSnapshot = dataSnapshot.child("trips");
for (DataSnapshot tripDataSnapshot : tripsDataSnapshot.getChildren()) {
Trip trip = new Trip();
trip.setTitle((String) tripDataSnapshot.child("title").getValue());
trip.setDescription((String) tripDataSnapshot.child("description").getValue());
DataSnapshot dateDataSnapshot = tripDataSnapshot.child("date");
Date date = new Date();
if (dateDataSnapshot.child("time").getValue() != null) {
date.setTime((Long) dateDataSnapshot.child("time").getValue());
}
trip.setDate(date);
DataSnapshot imagesDataSnapshot = tripDataSnapshot.child("images");
List<String> imageList = new ArrayList<>();
for (int i = 1; i <= imagesDataSnapshot.getChildrenCount(); i++) {
imageList.add(String.valueOf(imagesDataSnapshot.child("img" + i).getValue()));
}
trip.setImages(imageList);
DataSnapshot placesDataSnapshot = tripDataSnapshot.child("places");
List<Place> placeList = new ArrayList<>();
for (int i = 0; i < placesDataSnapshot.getChildrenCount(); i++) {
Place place = new Place();
place.setLat((String) placesDataSnapshot.child(String.valueOf(i)).child("lat").getValue());
place.setLng((String) placesDataSnapshot.child(String.valueOf(i)).child("lng").getValue());
placeList.add(place);
}
trip.setPlaces(placeList);
Log.d(TripListActivity.class.getSimpleName(), "Trip date = " + date.getTime() + " current time = " + currentTime);
if (currentTime - date.getTime() <= SEVEN_DAYS_IN_MILISECONDS) {
recentTrips.add(trip);
//get first image form each trip
imageRefsRecent.add(firebaseStorage.getReferenceFromUrl(imageList.get(0)));
} else {
pastTrips.add(trip);
//get first image form each trip
imageRefsPast.add(firebaseStorage.getReferenceFromUrl(imageList.get(0)));
}
}
provideRecentTripsUI();
}
@Override
public void onCancelled(@NonNull DatabaseError databaseError) {
Log.d("-----Error-----", databaseError.getMessage());
}
});
}
private void populateTripList(final List<Trip> tripList, List<StorageReference> imageRefs) {
TripAdapter tripAdapter = new TripAdapter(tripList, imageRefs, getApplicationContext());
RecyclerView.LayoutManager layoutManager = new GridLayoutManager(getApplicationContext(), 2);
//set on item click listener
tripAdapter.setItemClickListener(new TripAdapter.ItemClickListener() {
@Override
public void onItemClick(View view, int position) {
SharedPreferences.Editor sharedPreferencesEditor = getSharedPreferences(SHARED_PREFERENCES, MODE_PRIVATE).edit();
sharedPreferencesEditor.putString(TRIP_CLICKED_TITLE, tripList.get(position).getTitle());
sharedPreferencesEditor.putString(TRIP_CLICKED_DESCRIPTION, tripList.get(position).getDescription());
sharedPreferencesEditor.apply();
Intent tripDetailIntent = new Intent(TripListActivity.this, TripDetailActivity.class);
tripDetailIntent.putExtra("tripClicked", tripList.get(position));
tripDetailIntent.putExtra("tripId", position + 1);
tripDetailIntent.putExtra("userUID", firebaseAuth.getCurrentUser().getUid());
startActivity(tripDetailIntent);
}
});
rlvTrips.setLayoutManager(layoutManager);
rlvTrips.setItemAnimator(new DefaultItemAnimator());
rlvTrips.setAdapter(tripAdapter);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch (id) {
case R.id.recentTrips:
provideRecentTripsUI();
return true;
case R.id.pastTrips:
providePastTripsUI();
return true;
case R.id.addTrip:
Intent intent = new Intent(this, TripAdderActivity.class);
startActivity(intent);
return true;
default:
return super.onOptionsItemSelected(item);
}
}
private void provideRecentTripsUI() {
if (recentTrips.size() != 0) {
setContentView(R.layout.activity_trip_list);
ButterKnife.bind(TripListActivity.this);
populateTripList(recentTrips, imageRefsRecent);
} else {
setContentView(R.layout.no_recent_trips_layout);
}
}
public void allTripsMode(View view) {
providePastTripsUI();
}
private void providePastTripsUI() {
setContentView(R.layout.activity_trip_list);
ButterKnife.bind(TripListActivity.this);
populateTripList(pastTrips, imageRefsPast);
if (pastTrips.size() == 0) {
ToastUtil.showToast("No past trips!", this);
}
}
}
TripAdderActivity.java
//removed imports due to body limitation at 30000 chas
public class TripAdderActivity extends AppCompatActivity {
@BindView(R.id.etTitle)
EditText etTitle;
@BindView(R.id.etDescription)
EditText etDescription;
@BindView(R.id.lvMedia)
ListView lvMedia;
private FirebaseAuth firebaseAuth;
private FirebaseStorage firebaseStorage;
private DatabaseReference databaseReference;
private ArrayList<Uri> imageURIs;
private Trip trip;
private Date date;
private long tripId;
public static final int PICK_IMAGE_REQUEST = 1;
private String imageEncoded;
private List<String> imagesEncodedList;
static boolean placesAdded = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_trip_adder);
//bind views
ButterKnife.bind(this);
trip = new Trip();
imageURIs = new ArrayList<>();
//get current time
date = Calendar.getInstance().getTime();
//create instance of firebase auth
firebaseAuth = FirebaseAuth.getInstance();
//create instance of firebase storage
firebaseStorage = FirebaseStorage.getInstance();
//get database reference
databaseReference = FirebaseDatabase.getInstance().getReference();
//read number of trips from the database
databaseReference.child("users/" + firebaseAuth.getCurrentUser().getUid() + "/").addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot dataSnapshot) {
tripId = (long) dataSnapshot.child("tripNumber").getValue();
}
@Override
public void onCancelled(DatabaseError error) {
// Failed to read value
Log.w(TripListActivity.class.getSimpleName(), "Failed to read trip.");
}
});
}
/**
* @param view This method sends the user to a MapActivity.
*/
public void addPlace(View view) {
Intent intent = new Intent(this, MapsAdderActivity.class);
intent.putExtra("tripId", tripId);
startActivity(intent);
}
/**
* @param view This method uses an intent to allow the user to pick images that he wants to add to the Trip object
* and stores the images in firebase storage.
*/
public void addMedia(View view) {
(new AddImagesTask() {
@Override
protected void onPreExecute() {
super.onPreExecute();
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
}).execute();
Intent intent = new Intent();
intent.setType("image/*");
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
intent.setAction(Intent.ACTION_GET_CONTENT);
startActivityForResult(Intent.createChooser(intent, "Select Picture"), PICK_IMAGE_REQUEST);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
try {
// When an Image is picked
if (requestCode == PICK_IMAGE_REQUEST && resultCode == RESULT_OK
&& null != data) {
// Get the Image from data
String[] filePathColumn = {MediaStore.Images.Media.DATA};
imagesEncodedList = new ArrayList<String>();
if (data.getData() != null) {
Uri mImageUri = data.getData();
// Get the cursor
Cursor cursor = getContentResolver().query(mImageUri,
filePathColumn, null, null, null);
// Move to first row
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
imageEncoded = cursor.getString(columnIndex);
cursor.close();
} else {
if (data.getClipData() != null) {
ClipData mClipData = data.getClipData();
ArrayList<Uri> mArrayUri = new ArrayList<Uri>();
for (int i = 0; i < mClipData.getItemCount(); i++) {
ClipData.Item item = mClipData.getItemAt(i);
Uri uri = item.getUri();
mArrayUri.add(uri);
// Get the cursor
Cursor cursor = getContentResolver().query(uri, filePathColumn, null, null, null);
// Move to first row
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
imageEncoded = cursor.getString(columnIndex);
imagesEncodedList.add(imageEncoded);
cursor.close();
}
Log.v("LOG_TAG", "Selected Images" + mArrayUri.size());
imageURIs = mArrayUri;
uploadImagesToFirebase();
}
}
} else {
ToastUtil.showToast("You haven't picked Image", this);
}
} catch (Exception e) {
ToastUtil.showToast("Something went wrong", this);
}
super.onActivityResult(requestCode, resultCode, data);
}
/**
* This method is used to upload images to Firebase Storage.
*/
private void uploadImagesToFirebase() {
//create storage reference from our app
//points to the root reference
StorageReference storageReference = firebaseStorage.getReference();
//create storage reference for user folder
//points to the trip folder
StorageReference userReference = storageReference.child("user/" + firebaseAuth.getCurrentUser().getUid()).child("trips").child("trip" + tripId);
StorageReference imageReference;
UploadTask uploadTask;
//array list used to store images paths
final ArrayList<String> strings = new ArrayList<>();
int i = 0;
for (Uri imageURI : imageURIs) {
//create storage reference for user's image folder
//points to the images folder
imageReference = userReference.child("images/" + "img" + i);
i++;
uploadTask = imageReference.putFile(imageURI);
strings.add(imageURI.getPath());
// Register observers to listen for when the download is done or if it fails
uploadTask.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception exception) {
}
}).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
@Override
public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getApplicationContext(), android.R.layout.simple_list_item_1, android.R.id.text1, strings);
lvMedia.setAdapter(adapter);
}
});
databaseReference.child("users").child(firebaseAuth.getUid()).child("trips").child("trip" + tripId).child("images").child("img" + i).setValue(imageReference.toString());
}
}
/**
* @param view This method saves the Trip object to firebase database.
*/
public void saveTrip(View view) {
String title = null;
String description = null;
boolean ok;
if ((!etTitle.getText().toString().isEmpty()) &&
(!etDescription.getText().toString().isEmpty()) &&
(imageURIs.size() != 0) &&
(placesAdded)) {
title = etTitle.getText().toString();
description = etDescription.getText().toString();
ok = true;
placesAdded = false;
} else {
ok = false;
}
if (ok) {
trip.setTitle(title);
trip.setDescription(description);
trip.setDate(date);
databaseReference.child("users").child(firebaseAuth.getUid()).child("trips").child("trip" + tripId).child("title").setValue(trip.getTitle());
databaseReference.child("users").child(firebaseAuth.getUid()).child("trips").child("trip" + tripId).child("description").setValue(trip.getDescription());
databaseReference.child("users").child(firebaseAuth.getUid()).child("trips").child("trip" + tripId).child("date").setValue(trip.getDate());
tripId++;
databaseReference.child("users").child(firebaseAuth.getUid()).child("tripNumber").setValue(tripId);
ToastUtil.showToast("Trip saved!", getApplicationContext());
Log.d(TripAdderActivity.class.getSimpleName(), "Current trip id = " + tripId);
Intent intentRecentTrips = new Intent(this, TripListActivity.class);
intentRecentTrips.putExtra("tripId", tripId);
startActivity(intentRecentTrips);
} else {
ToastUtil.showToast("Trip couldn't be saved! Please check fields!", getApplicationContext());
}
}
}
MapsAdderActivity.java
public class MapsAdderActivity extends FragmentActivity implements OnMapReadyCallback {
private GoogleMap mMap;
private List<Place> places;
private Place place;
private int placeId = 0;
private long tripId;
private DatabaseReference databaseReference;
private FirebaseAuth firebaseAuth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_maps_adder);
//array list used to store the places added
places = new ArrayList<>();
//get database reference
databaseReference = FirebaseDatabase.getInstance().getReference();
//create instance of firebase auth
firebaseAuth = FirebaseAuth.getInstance();
Bundle bundle = getIntent().getExtras();
if (bundle != null) {
tripId = bundle.getLong("tripId");
}
SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager()
.findFragmentById(R.id.map);
mapFragment.getMapAsync(this);
}
@Override
public void onMapReady(GoogleMap googleMap) {
//move the camera to the center of the map
mMap = googleMap;
mMap.moveCamera(CameraUpdateFactory.newLatLng(new LatLng(0, 0)));
mMap.setOnMapClickListener(new GoogleMap.OnMapClickListener() {
@Override
public void onMapClick(LatLng latLng) {
// Creating a marker
MarkerOptions markerOptions = new MarkerOptions();
// Setting the position for the marker
markerOptions.position(latLng);
// Setting the title for the marker.
// This will be displayed on taping the marker
markerOptions.title(latLng.latitude + " : " + latLng.longitude);
// Clears the previously touched position
mMap.clear();
// Animating to the touched position
mMap.animateCamera(CameraUpdateFactory.newLatLng(latLng));
// Placing a marker on the touched position
mMap.addMarker(markerOptions);
place = new Place(Double.toString(latLng.latitude), Double.toString(latLng.longitude));
}
});
}
public void addMarkerToMap(View view) {
places.add(place);
}
public void saveMarkers(View view) {
for (int i = placeId; i < places.size(); i++) {
writeNewPlace(places.get(i).getLat(), places.get(i).getLng());
}
TripAdderActivity.placesAdded = true;
ToastUtil.showToast("Places added!", getApplicationContext());
}
private void writeNewPlace(String lat, String lng) {
Place place = new Place(lat, lng);
databaseReference.child("users").child(firebaseAuth.getUid()).child("trips").child("trip" + tripId).child("places").child(String.valueOf(placeId)).setValue(place);
placeId++;
}
public void cleanMarkers(View view) {
places.clear();
placeId = 0;
databaseReference.child("users").child(firebaseAuth.getUid()).child("trips").child("trip" + tripId).child("places").removeValue();
}
}
activity_maps_adder.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fillViewport="true"
android:orientation="vertical"
android:scrollbars="none"
tools:context=".MapsAdderActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:padding="8dp">
<fragment
android:id="@+id/map"
android:name="com.google.android.gms.maps.SupportMapFragment"
android:layout_width="match_parent"
android:layout_height="400dp"
tools:context=".MapsAdderActivity" />
</FrameLayout>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="addMarkerToMap"
android:text="@string/add_marker" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="cleanMarkers"
android:text="@string/clean_markers" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:onClick="saveMarkers"
android:text="@string/save_markers" />
</LinearLayout>
</ScrollView>
The problem is caused by pressing the saveMarkers
button from MapsAdderActivity
.
How things should work:
Let's assume the app starts with TripListActivity
(I have a sign-in activity prior to this that works fine). By pressing the button Add a trip
from the menu you are redirected to TripAdderActivity
. From here you are able to add places to your new trip (they will be stored in Firebase Database). Pressing Add places
button will take you to MapsAdderActivity
. You should add a marked on the googleMap
by tapping the screen, Add marker
simply saves the marker in a list, while Save markers
will save them in Firebase Database.
The error I get:
If I try to add more markers (let's say two, so I add two place objects into the places
list) and save them, pressing Save markers
button will cause the MapsAdderActivity
to finish (or something similar). Moreover, if MapsAdderActivity
is finished, the app should go back to TripAdderActivity
(from my point of view), but it returns to TripListActivity
where I get a logic error and crashes (each trip requires an image and not uploading one will cause an error).
So pressing Save markers
(saveMarkers
method) will somehow redirect me to TripListActivity
.
Here is a recording of how things evolve.
In the backend everything works fine, the markers are saved:
As you can see in trip2 there are 2 correct 'place' objects.
08-06 17:08:24.110 3293-3293/com.grrigore.tripback_up E/onStart ------: TripListActivity: onStart()
08-06 17:08:24.125 3293-3293/com.grrigore.tripback_up E/onResume ------: TripListActivity: onResume()
08-06 17:08:29.832 3293-3293/com.grrigore.tripback_up E/onPause ------: TripListActivity: onPause()
08-06 17:08:29.916 3293-3293/com.grrigore.tripback_up E/onStart ------: TripAdderActivity: onStart()
08-06 17:08:29.921 3293-3293/com.grrigore.tripback_up E/onResume ------: TripAdderActivity: onResume()
08-06 17:08:30.479 3293-3293/com.grrigore.tripback_up E/onStop ------: TripListActivity: onStop()
08-06 17:08:38.158 3293-3293/com.grrigore.tripback_up E/onPause ------: TripAdderActivity: onPause()
08-06 17:08:38.806 3293-3293/com.grrigore.tripback_up E/art: The String#value field is not present on Android versions >= 6.0
08-06 17:08:39.281 3293-3293/com.grrigore.tripback_up E/onStart ------: MapsAdderActivity: onStart()
08-06 17:08:39.286 3293-3293/com.grrigore.tripback_up E/onResume ------: MapsAdderActivity: onResume()
08-06 17:08:39.943 3293-3293/com.grrigore.tripback_up E/onStop ------: TripAdderActivity: onStop()
08-06 17:09:06.030 3293-3293/com.grrigore.tripback_up E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.grrigore.tripback_up, PID: 3293
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.get(ArrayList.java:411)
at com.grrigore.tripback_up.TripListActivity$1.onDataChange(TripListActivity.java:119)
at com.google.android.gms.internal.firebase_database.zzfc.zza(Unknown Source)
at com.google.android.gms.internal.firebase_database.zzgx.zzdr(Unknown Source)
at com.google.android.gms.internal.firebase_database.zzhd.run(Unknown Source)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6816)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1563)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1451)
This crash is because I didn't add images to my trip object, it will try to get the first image in a list but there are no objects in the list.
The reason the app crashes it's obvious, but I don't understand this behaviour (closing current activity at the press of Save markers
button).
Any idea?
LE: Seems that I get the error even if I only add one place. The fact that I was updating the places didn't cause the crash.
TripListActivity
causes a logicarrayoutofboundsexception
.TripAdderActivity
is finishing too. I can't see your pastebins as it's blocked at work. You should probably upload them to your question anyway to keep everything in-house.placeId
(which really is theplaceCount
), useplaces.size()
, it will help you avoid decoupling.