ConnectOut-sdp/sdp2023

View on GitHub
app/src/main/java/com/sdpteam/connectout/profile/ProfileFirebaseDataSource.java

Summary

Maintainability
A
0 mins
Test Coverage
A
97%
package com.sdpteam.connectout.profile;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.DatabaseReference;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.Query;
import com.google.firebase.database.ValueEventListener;
import com.sdpteam.connectout.profileList.filter.ProfileFilter;

public class ProfileFirebaseDataSource implements ProfileDataSource, RegisteredEventsDataSource {
    public final static String USERS = "Users";
    public final static String PROFILE = "Profile";
    private final static int MAX_PROFILES_FETCHED = 50;
    private final String REGISTERED_EVENTS = "RegisteredEvents";
    private final DatabaseReference firebaseRef;

    public ProfileFirebaseDataSource() {
        firebaseRef = FirebaseDatabase.getInstance().getReference();
    }

    /**
     * Saves the given profile in firebase
     *
     * @param profile (Profile): profile to save into firebase
     * @return (CompletableFuture < Boolean >): completes to true if profile is saved
     */
    @Override
    public CompletableFuture<Boolean> saveProfile(Profile profile) {
        CompletableFuture<Boolean> finished = new CompletableFuture<>();
        firebaseRef.child(USERS)
                .child(profile.getId())
                .child(PROFILE)
                .setValue(profile)
                .addOnCompleteListener(command -> finished.complete(command.isSuccessful()));
        return finished;
    }

    /**
     * Retrieves a Profile from firebase using its Id.
     *
     * @param userId (String): id of the user to find
     * @return (CompletableFuture < Profile >): completes to the matching profile Id
     */
    @Override
    public CompletableFuture<Profile> fetchProfile(String userId) {
        CompletableFuture<Profile> future = new CompletableFuture<>();
        fetchProfiles(new ArrayList<>(Collections.singletonList(userId)))
                //List has at least a null element.
                .thenApply(profiles -> future.complete(profiles.isEmpty() ? null : profiles.get(0)));
        return future;
    }

    /**
     * Retrieves a list of profiles from firebase using their Ids.
     *
     * @param userIds (List<String>): id of the user to find
     * @return (CompletableFuture < List < Profile > >): completes to the matching list of profiles Ids
     */
    public CompletableFuture<List<Profile>> fetchProfiles(List<String> userIds) {
        CompletableFuture<List<Profile>> futures = new CompletableFuture<>();
        List<Task<DataSnapshot>> tasks = new ArrayList<>();

        for (String userId : userIds) {
            tasks.add(firebaseRef.child(USERS).child(userId).child(PROFILE).get());
        }

        Task<List<DataSnapshot>> allTasks = Tasks.whenAllSuccess(tasks);
        List<Profile> profiles = new ArrayList<>();
        allTasks.addOnCompleteListener(dataSnapshots -> {
            for (DataSnapshot dataSnapshot : dataSnapshots.getResult()) {
                Profile profile = dataSnapshot.getValue(Profile.class);
                if (profile != null) {
                    profiles.add(profile);
                }
            }
            futures.complete(profiles);
        });
        return futures;
    }

    @Override
    public CompletableFuture<List<Profile>> getProfilesByFilter(ProfileFilter filter) {
        final CompletableFuture<List<Profile>> future = new CompletableFuture<>();

        // Fetch all the users (profile + its registered events)
        firebaseRef.child(USERS).limitToFirst(MAX_PROFILES_FETCHED).get().addOnCompleteListener(t -> {
            final List<Profile> result = new ArrayList<>();

            // Iterate over the results
            for (DataSnapshot snapshot : t.getResult().getChildren()) {

                // Get the profile class
                final Profile profile = snapshot.child(PROFILE).getValue(Profile.class);
                if (profile == null || profile.getNameLowercase() == null) {
                    continue;
                }

                // Get the registered events list
                final List<RegisteredEvent> registeredEvents = new ArrayList<>();
                for (DataSnapshot child : snapshot.child(REGISTERED_EVENTS).getChildren()) {
                    final RegisteredEvent event = child.getValue(RegisteredEvent.class);
                    registeredEvents.add(event);
                }

                // Check if the entry satisfies the filter
                final ProfileEntry entry = new ProfileEntry(profile, registeredEvents);
                if (filter.test(entry)) {
                    result.add(entry.getProfile());
                }
            }
            future.complete(result);
        });
        return future;
    }

    public void deleteProfile(String uid) {
        firebaseRef.child(USERS).child(uid).removeValue();
    }

    /**
     * stores a new Profile.RegisteredEvent (eventId, eventTitle and eventDate)
     * in list of events that a profile is registered to
     * this list of events is stored under USERS/profileId/REGISTERED_EVENTS
     * each RegisteredEvent has an auto generated key
     * We make sure that a Calendar Event isn't already in the User's memory before adding it
     * Not very efficient if the user is registered to A LOT of events. Which won't be the case so we re fine
     */
    public void registerToEvent(RegisteredEvent calEvent, String profileId) {
        Query q = firebaseRef.child(USERS)
                .child(profileId)
                .child(REGISTERED_EVENTS)
                .child(calEvent.getEventId());
        q.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                if (!dataSnapshot.exists()) {
                    firebaseRef.child(USERS)
                            .child(profileId)
                            .child(REGISTERED_EVENTS)
                            .child(calEvent.getEventId())
                            .setValue(calEvent);
                }
            }

            @Override
            public void onCancelled(@androidx.annotation.NonNull DatabaseError error) {
                //do nothing
            }
        });
    }

    public void unregisterToEvent(String eventId, String profileId) {
        firebaseRef.child(USERS)
                .child(profileId)
                .child(REGISTERED_EVENTS)
                .child(eventId).removeValue();
    }
}