3

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:

firebase database saved places

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.

10
  • Please add your crash logs.
    – vguzzi
    Commented Aug 6, 2018 at 13:24
  • @vguzzi Will do, but as I mentioned the crash it's not a problem, but the redirect. Redirecting me to TripListActivity causes a logic arrayoutofboundsexception.
    – grrigore
    Commented Aug 6, 2018 at 13:26
  • The problem will be discoverable in the stack trace, maybe 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.
    – vguzzi
    Commented Aug 6, 2018 at 13:30
  • Please add the error and the line on which occurs.
    – Alex Mamo
    Commented Aug 6, 2018 at 13:30
  • 1
    Instead of using placeId (which really is the placeCount), use places.size(), it will help you avoid decoupling.
    – vguzzi
    Commented Aug 6, 2018 at 14:00

1 Answer 1

3

onDataChange will continue to be invoked if you don't remove the listener when you're done using it. You need to remove your onDataChange listener when you're done with it, or you'll get weird behavior like you describe.

You can remove it by calling

databaseReference.removeEventListener(this);

In the callback

1
  • I found this great tutorial explaining almost everything about event listeners.
    – grrigore
    Commented Sep 5, 2018 at 19:02

Not the answer you're looking for? Browse other questions tagged or ask your own question.