realm / realm-java

Realm is a mobile database: a replacement for SQLite & ORMs
http://realm.io
Apache License 2.0
11.45k stars 1.74k forks source link

Accessing Elements within RealmList #4222

Closed DougSteiert closed 7 years ago

DougSteiert commented 7 years ago

I am having a problem correctly accessing the elements within a RealmList of mine. To begin, I will give some of my details necessary to understand the issue.

I have a base class:

public class Semantics extends RealmObject {
    public String type;
    public double lat;
    public double lng;
    public String name;

    // getters and setters
}

Then an outer class:

public class Trace extends RealmObject {
    public RealmList<Semantics> singleTrace;
    private int ID;
    // getters and setters`
}

Then another layer class:

public class Session extends RealmObject { 
    public RealmList<Location> realLocations;
    public RealmList<Trace> multipleTraces;
    public RealmList<Location> mockLocations;
    public RealmList<Location> mobilityTrace;
    public RealmList<Preference> preferences;
    // getters and setters
}

In my code, I get a list of random locations (works correctly) and cycle through this list to find nearby place types, per Google Places API. Here is some code to show:

for (XMLAttributes data : randLocs) {
        if(!trace.getSingleTrace().isEmpty()) {
            realm.beginTransaction();
            trace.getSingleTrace().deleteAllFromRealm();
            realm.commitTransaction();
        }
        cityNames.add(data.getName());
        cityCoords.add(new LatLng(data.getLat(), data.getLng()));
        //Log.i("Fake city", data.getName());

        if(!realm.isClosed()) {
            realm.beginTransaction();
            trace.setID(id);
            realm.commitTransaction();
        }

        Location loc = new Location("");
        loc.setLatitude(data.getLat());
        loc.setLongitude(data.getLng());
        randTypeIndex = rand.nextInt(manualPlaceTypes.size());
        places = findNearbyPlaces.findNearbyPlaces(loc, manualPlaceTypes.get(randTypeIndex));

        for(PlaceAttributes place: places) {
            Semantics temp = new Semantics();
            if(place.getName() != null) {
                temp.setLat(place.getLat());
                temp.setLng(place.getLng());
                temp.setName(place.getName());

                if(!realm.isClosed()) {
                    realm.beginTransaction();
                    trace.addNewSemanticTrace(temp);
                    realm.commitTransaction();
                }
            }
            Log.i("WHOA", temp.getName());
        }

        if(!trace.getSingleTrace().isEmpty()) {
            if(!realm.isClosed()) {
                realm.beginTransaction();
                Log.i("ID", String.valueOf(trace.getID()));
                session.addNewMultipleTrace(trace);
                realm.commitTransaction();
            }
        }

        id++;
    }`

I know it's quite a bit, but I feel this is necessary to understand what I do. As you can see, for each location in which there are places found, I set an ID to the Trace and add each place to the trace. Then, at the end, I add that RealmList to another RealmList, as per another user told me I could do.

The problem forms when I try accessing this list later on with: `

if(!session.getMultipleTraces().isEmpty()) {
        int i=1;
        for(Trace traces : session.getMultipleTraces()) {
            Log.i("ID",String.valueOf(traces.getID()));
            if(!traces.getSingleTrace().isEmpty()) {
                for(Semantics place : traces.getSingleTrace()) {
                    if(place.getName() != null) {
                        Log.i("MultTraces" +i, String.valueOf(place.getName()));
                    }
                }
            }

            i++;
        }
    }`

My output is such:

 02-22 12:08:20.137 17149-17149/com.djsg38.locationprivacyapp I/ID: 6
 02-22 12:08:20.137 17149-17149/com.djsg38.locationprivacyapp I/MultTraces1: Casey's General Store
 02-22 12:08:20.137 17149-17149/com.djsg38.locationprivacyapp I/MultTraces1: Phillips 66
 02-22 12:08:20.138 17149-17149/com.djsg38.locationprivacyapp I/MultTraces1: CHUCKS SHORT STOP
 02-22 12:08:20.138 17149-17149/com.djsg38.locationprivacyapp I/MultTraces1: Phillips 66
 02-22 12:08:20.138 17149-17149/com.djsg38.locationprivacyapp I/MultTraces1: BOZLESS   CONVENIENCE
 02-22 12:08:20.138 17149-17149/com.djsg38.locationprivacyapp I/ID: 6
 02-22 12:08:20.138 17149-17149/com.djsg38.locationprivacyapp I/MultTraces2: Casey's General Store
 02-22 12:08:20.138 17149-17149/com.djsg38.locationprivacyapp I/MultTraces2: Phillips 66
 02-22 12:08:20.138 17149-17149/com.djsg38.locationprivacyapp I/MultTraces2: CHUCKS SHORT STOP
 02-22 12:08:20.139 17149-17149/com.djsg38.locationprivacyapp I/MultTraces2: Phillips 66
 02-22 12:08:20.139 17149-17149/com.djsg38.locationprivacyapp I/MultTraces2: BOZLESS CONVENIENCE`

It finds two traces, which I think is correct, since only two locations had places. However, the traces are the exact same with the same IDs. Instead, one trace should have ID = 2 and one place Landmark Plaza Frontenac, which isn't even shown in these traces. Am I doing something wrong when adding the traces?

Update: It seems that it keeps outputting the trace and ID of the LAST inserted ID and trace; it does not get the IDs nor traces of ALL the traces inserted. Not sure if I am just doing it wrong??

zaki50 commented 7 years ago

In the for (XMLAttributes data : randLocs) { loop, you seem to be using the same trace object. If it's true, you are overwriting existing object and then add it to the session.

DougSteiert commented 7 years ago

@zaki50 Hmm interesting. So my overall goal is to basically have a "temporary" trace object, in which I add Places to it with attributes according to Semantics, then I want to dump that trace object into a Session RealmList, but I want a new Trace object each loop, I don't want to keep using the old values in the Trace object, because then the mobility trace will be wrong if it includes Places from the previous iteration of the loop. Is there a more correct way to dump these trace values in?

zaki50 commented 7 years ago

@DougSteiert Can you show us the entire class which contains for (XMLAttributes data : randLocs) { loop?

Zhuinden commented 7 years ago

You really need to start making those 4 transactions per element actually be a single transaction for all elements...

DougSteiert commented 7 years ago

@Zhuinden I am now in the process of making the "for loop" a single transaction. I do not believe making this entire class a transaction is a good idea. My reasoning is that this class is acting upon a Service of Android and continuously on, so a transaction would ALWAYS be on until the service is stopped if I make it one transaction. Below is the entire, now updated, class..almost 400 lines @zaki50

public class LocationAnonymizer implements GoogleApiClient.ConnectionCallbacks, GoogleApiClient.OnConnectionFailedListener, LocationListener {

    Context context;

    LocationRequest mLocationRequest;
    GoogleApiClient mGoogleApiClient;
    AnonymizationService anonymizationService;
    private static Random rand;
    private int realLocUse;
    private int randIndex;

    // Create manual place types to cycle through for first-round testing
    private ArrayList<String> manualPlaceTypes = new ArrayList<>();

    private int count = 0;

    com.djsg38.locationprivacyapp.models.Location currentLoc;

    Realm realm;

    GenerateNearbyCities cityGen;
    ArrayList<XMLAttributes> randLocs;
    ArrayList<String> cityNames;
    ArrayList<LatLng> cityCoords;

    ConvertPlaceTypes convertPlaceTypes;
    FindNearbyPlaces findNearbyPlaces;
    ArrayList<PlaceAttributes> places;
    List<Integer> types;
    List<String> typeStrings;

    Handler handler = new Handler();
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            updateMockLocation();
            Double randTime = LocationAnonymizer.rand.nextDouble() * 29000;
            int time = randTime.intValue() + 1000;

            // 30000 = 30s approx
            if(handler != null) handler.postDelayed(this, time);
        }
    };

    public LocationAnonymizer(Context context, AnonymizationService anonymizationService, Integer kValue) throws ExecutionException, InterruptedException {
        this.context = context;
        this.anonymizationService = anonymizationService;

        realm = Realm.getDefaultInstance();
        Session session = realm.where(Session.class).findFirst();
        Trace trace = realm.where(Trace.class).findFirst();

        // Add some values for manual testing
        manualPlaceTypes.add("restaurant");
        manualPlaceTypes.add("university");
        manualPlaceTypes.add("gas_station");
        manualPlaceTypes.add("movie_theater");
        manualPlaceTypes.add("cafe");
        manualPlaceTypes.add("health");

        findNearbyPlaces = new FindNearbyPlaces();
        places = new ArrayList<>();

        rand = new Random();

        cityGen = new GenerateNearbyCities();
        randLocs = cityGen.generateLocations(kValue);
        cityNames = new ArrayList<>();
        cityCoords = new ArrayList<>();

        int randTypeIndex;

        if(!session.getMultipleTraces().isEmpty()) {
            realm.beginTransaction();
            session.getMultipleTraces().deleteAllFromRealm();
            realm.commitTransaction();
        }

        // randLocs will be empty if kvalue was 1, which then causes mock locations to break
        // as getting a random index on something of size 0 fails

        int id = 2;
        realm.beginTransaction();
        for (XMLAttributes data : randLocs) {
            if(!trace.getSingleTrace().isEmpty()) {
                trace.getSingleTrace().clear();
            }

            cityNames.add(data.getName());
            cityCoords.add(new LatLng(data.getLat(), data.getLng()));
            //Log.i("Fake city", data.getName());

            Location loc = new Location("");
            loc.setLatitude(data.getLat());
            loc.setLongitude(data.getLng());
            randTypeIndex = rand.nextInt(manualPlaceTypes.size());
            places = findNearbyPlaces.findNearbyPlaces(loc, manualPlaceTypes.get(randTypeIndex));

            for(PlaceAttributes place: places) {
                Semantics temp = new Semantics();
                if(place.getName() != null) {
                    temp.setLat(place.getLat());
                    temp.setLng(place.getLng());
                    temp.setName(place.getName());

                    if(!realm.isClosed()) {
                        trace.setID(id);
                        trace.addNewSemanticTrace(temp);
                    }
                }
                Log.i("WHOA", temp.getName());
            }

            if(!trace.getSingleTrace().isEmpty()) {
                if(!realm.isClosed()) {
                    Log.i("ID", String.valueOf(trace.getID()));
                    for(Semantics place : trace.getSingleTrace()) {
                        Log.i("Trace", place.getName());
                    }
                    session.addNewMultipleTrace(trace);
                }
            }

            id++;
        }
        realm.commitTransaction();

        if(!session.getMultipleTraces().isEmpty()) {
            Log.i("size", String.valueOf(session.getMultipleTraces().size()));
            int i=1;
            for(Trace traces : session.getMultipleTraces()) {
                Log.i("IDhi",String.valueOf(traces.getID()));
                if(!traces.getSingleTrace().isEmpty()) {
                    for(Semantics place : traces.getSingleTrace()) {
                        if(place.getName() != null) {
                            Log.i("MultTraces" +i, String.valueOf(place.getName()));
                        }
                    }
                 }

                i++;
            }
        }

        currentLoc = new com.djsg38.locationprivacyapp.models.Location();
        // real locations is potentially empty
        currentLoc.setLat(session.getRealLocations().last().getLat());
        currentLoc.setLong(session.getRealLocations().last().getLong());

        Log.i("CurrentLoc", String.valueOf(currentLoc.getLat()) + ',' + String.valueOf(currentLoc.getLong()));

        createLocationRequest();
        buildGoogleApiClient();
        mGoogleApiClient.connect();
    }

    // Initiate a timer for logging location
    public void startTimer() {
        Log.i("Timer", "Started");
        runnable.run();
    }

    // Stop faking the location
    public void stopMockLocs() {
        handler.removeCallbacksAndMessages(null);
        runnable = null;
        handler = null;
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        if(mGoogleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.setMockMode(mGoogleApiClient, false);
            LocationServices.FusedLocationApi.removeLocationUpdates(mGoogleApiClient, this);
        }

        realm.close();
    }

    // Initialize the ability to set mock locations
    private void setMockLocation() {
        try {
            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                 // TODO: Consider calling
                 //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                return;
            }
            LocationServices.FusedLocationApi.setMockMode(mGoogleApiClient, true);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Update the current mocked location to a new value
    private void updateMockLocation() {
         randIndex = rand.nextInt(cityCoords.size());

        // Don't ask, don't tell
        // Just random number divisible by 3
        realLocUse = rand.nextInt(51);
        // Another method to ensure app doesn't go too long without using real loc

        Location mockLoc = new Location(LocationManager.NETWORK_PROVIDER);

        // If the number is divisible by 3, then go ahead and use the real location (random choice)
        if((((realLocUse % 3) == 0) && (count > 2)) || count == 10) {

            mockLoc.setLatitude(currentLoc.getLat());
            mockLoc.setLongitude(currentLoc.getLong());
            Log.i("YO", "used real loc");
            count = 0;
        }
        else {
            mockLoc.setLatitude(cityCoords.get(randIndex).latitude);
            mockLoc.setLongitude(cityCoords.get(randIndex).longitude);
            count++;
        }

        mockLoc.setAccuracy(20);
        mockLoc.setTime(System.currentTimeMillis());
        mockLoc.setElapsedRealtimeNanos(System.nanoTime());

        Log.i("MockedLoc", mockLoc.toString());

        try {
            if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
                // TODO: Consider calling
                //    ActivityCompat#requestPermissions
                // here to request the missing permissions, and then overriding
                //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
                //                                          int[] grantResults)
                // to handle the case where the user grants the permission. See the documentation
                // for ActivityCompat#requestPermissions for more details.
                return;
            }
            LocationServices.FusedLocationApi.setMockLocation(mGoogleApiClient, mockLoc);
            if(!realm.isClosed()) {
                String time = getDate(System.currentTimeMillis(), "dd/MM/yyyy hh:mm:ss.SSS");
                realm.beginTransaction();
                realm.where(Session.class).findFirst().getMobilityTrace()
                        .add(new com.djsg38.locationprivacyapp.models.Location()
                                .setLat(mockLoc.getLatitude())
                                .setLong(mockLoc.getLongitude())
                                .setTime(time));
                realm.commitTransaction();
             }
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static String getDate(long milliSeconds, String dateFormat)
    {
        // Create a DateFormatter object for displaying date in specified format.
        SimpleDateFormat formatter = new SimpleDateFormat(dateFormat);

        // Create a calendar object that will convert the date and time value in milliseconds to date.
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(milliSeconds);
        return formatter.format(calendar.getTime());
    }

    // Begin faking the location
    public void initiateMockLocs() {
        setMockLocation();
        updateMockLocation();
        startTimer();
    }

    @Override
    public void onConnected(Bundle bundle) {
        if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
            // TODO: Consider calling
            //    ActivityCompat#requestPermissions
            // here to request the missing permissions, and then overriding
            //   public void onRequestPermissionsResult(int requestCode, String[] permissions,
            //                                          int[] grantResults)
            // to handle the case where the user grants the permission. See the documentation
            // for ActivityCompat#requestPermissions for more details.
            return;
        }
        LocationServices.FusedLocationApi.requestLocationUpdates(mGoogleApiClient, mLocationRequest, this);
        Log.i("GoogleApiClient", "Connected");

        initiateMockLocs();
    }

    @Override
    public void onConnectionSuspended(int i) {

    }

    @Override
    public void onConnectionFailed(ConnectionResult connectionResult) {

    }

    @Override
    public void onLocationChanged(final Location location) {
        Log.i("LocationChangedService", location.toString());

        Session session = realm.where(Session.class).findFirst();
        RealmList<com.djsg38.locationprivacyapp.models.Location>
                mockLocs = session.getMockLocations();

        if (mockLocs.where()
                .equalTo("Lat", location.getLatitude())
                .equalTo("Long", location.getLongitude()).findFirst() == null) {
            realm.beginTransaction();
            session.addNewMockLocation(location);
            realm.commitTransaction();
        }
    }

    // Initialize a GoogleApiClient object
    protected synchronized void buildGoogleApiClient() {
        mGoogleApiClient = new GoogleApiClient.Builder(context)
                .addApi(LocationServices.API)
                .addConnectionCallbacks(this)
                .addOnConnectionFailedListener(this)
                .build();
    }

    // Initialize a LocationRequest object
   protected void createLocationRequest() {
        mLocationRequest = new LocationRequest();
        mLocationRequest.setInterval(10000);
        mLocationRequest.setFastestInterval(10000);
        mLocationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
    }
}
kneth commented 7 years ago

I don't see any createObject() or copyToRealm(): how do you add objects in your Realm?

DougSteiert commented 7 years ago

I add new objects to a RealmList which is a member of Trace through the function trace.addNewSemanticTrace(temp);, but does a RealmList.add(item). But as per the other thread, I guess I need to initialize that RealmList, as those are unmanaged objects. Below is the code that does:

public class Trace extends RealmObject {
    public RealmList<Semantics> singleTrace;
    private int ID;

    public void setID(int id) {
        this.ID = id;
    }

    public int getID() {
        return ID;
    }

    public void addNewSemanticTrace(Semantics place) {
        singleTrace.add(place);
    }

    public RealmList<Semantics> getSingleTrace() {
        return singleTrace;
    }
}
DougSteiert commented 7 years ago

As per the other thread, I am closing this issue due to initialization of RealmList for unmanaged objects. I am so thankful for all of the help, especially to @zaki50 for helping me figure out I can have a RealmList of another Realm class which has a RealmList, to help fulfill my "List of Lists" idea.