SverreNystad / besieged

Besieged! is a cooperative, real-time multiplayer tower defense Android game, where players defend their village against waves of mythological creatures by strategically combining "tower-cards" to build unique towers, earning money from each kill to acquire more cards.
6 stars 1 forks source link

Create the DAO submodule #13

Closed SverreNystad closed 5 months ago

SverreNystad commented 6 months ago

The persistence submodule is a crucial component of our project, responsible for managing data access and manipulation across different storage solutions. This issue aims to outline the development and testing requirements for the persistence submodule, including the implementation of the DAO interface, DAOBuilder, and specific DAO implementations (LocalDAO, FirebaseDAO, MockDAO). Comprehensive tests are required to ensure reliability, functionality, and integration with Firebase and local storage solutions.

Acceptance Criteria:

SverreNystad commented 5 months ago

It seems like we now have added all needed dependencies for FireBase (Android Studio did not add all required dependencies). Now we need to write and read values from the real-time database.

I worked on the firebase and I realized that the DAO interface might not be the best for performance due to the nature of confirming if the actions FireBase.update(K id, T object) and FireBase.delete(K id) was successful. ChatGPT game this example on how to implement the remaining class but it is blocking actions, and as this is needed to be fast it might be problematic:

package com.softwarearchitecture.networking.persistence;

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.ValueEventListener;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class FirebaseDAO<K, T> extends DAO<K, T> {

    private FirebaseDatabase database;
    private String databasePath = "your_collection"; // Define your database path here

    public FirebaseDAO(boolean create, boolean read, boolean update, boolean delete) {
        this.create = create;
        this.read = read;
        this.update = update;
        this.delete = delete;

        String DatabaseName = "https://besieged-8b842-default-rtdb.europe-west1.firebasedatabase.app/";
        database = FirebaseDatabase.getInstance(DatabaseName);
    }

    @Override
    public List<T> loadAll() {
        CompletableFuture<List<T>> future = new CompletableFuture<>();
        DatabaseReference ref = database.getReference(databasePath);

        ref.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                List<T> result = new ArrayList<>();
                for (DataSnapshot snapshot : dataSnapshot.getChildren()) {
                    T element = snapshot.getValue(T.class);
                    result.add(element);
                }
                future.complete(result);
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                future.completeExceptionally(new Exception(databaseError.getMessage()));
            }
        });

        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    @Override
    public Optional<T> get(K id) {
        CompletableFuture<Optional<T>> future = new CompletableFuture<>();
        DatabaseReference ref = database.getReference(databasePath).child(id.toString());

        ref.addListenerForSingleValueEvent(new ValueEventListener() {
            @Override
            public void onDataChange(DataSnapshot dataSnapshot) {
                T object = dataSnapshot.getValue(T.class);
                future.complete(Optional.ofNullable(object));
            }

            @Override
            public void onCancelled(DatabaseError databaseError) {
                future.completeExceptionally(new Exception(databaseError.getMessage()));
            }
        });

        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean update(K id, T object) {
        CompletableFuture<Boolean> future = new CompletableFuture<>();
        DatabaseReference ref = database.getReference(databasePath).child(id.toString());

        ref.setValue(object, (databaseError, databaseReference) -> {
            if (databaseError != null) {
                future.completeExceptionally(new Exception(databaseError.getMessage()));
            } else {
                future.complete(true);
            }
        });

        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean delete(K id) {
        CompletableFuture<Boolean> future = new CompletableFuture<>();
        DatabaseReference ref = database.getReference(databasePath).child(id.toString());

        ref.removeValue((databaseError, databaseReference) -> {
            if (databaseError != null) {
                future.completeExceptionally(new Exception(databaseError.getMessage()));
            } else {
                future.complete(true);
            }
        });

        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }

    @Override
    public K add(T object) {
        DatabaseReference ref = database.getReference(databasePath);
        String key = ref.push().getKey();
        CompletableFuture<K> future = new CompletableFuture<>();
        ref.child(key).setValue(object, (databaseError, databaseReference) -> {
            if (databaseError != null) {
                future.completeExceptionally(new Exception(databaseError.getMessage()));
            } else {
                future.complete((K) key);
            }
        });

        try {
            return future.get();
        } catch (InterruptedException | ExecutionException e) {
            Thread.currentThread().interrupt();
            throw new RuntimeException(e);
        }
    }
}

Either we need to change the interface to make it not promise to confirm if the value actually was updated or deleted or the class must lie. I lean towards changing the interface.

SverreNystad commented 5 months ago

Firbase has an offline persistent mechnism in the case of loss of connecting making it so that we do not need a LocalDAO