4

So, I have the following method which is faking a database locally:

public class TestClassDao implements ClassDao {

    // ...

    private static List<ClassDto> classes = new ArrayList<>();

    @Override
    public List<ClassDto> getClassesByIds(List<Long> classIds) {
        List<ClassDto> results = new ArrayList<>();

        for (ClassDto classInstance : classes) {
            if (classIds.contains(classInstance.getId())) {
                results.add(classInstance);
            }
        }

        return cloner.deepClone(results);
   }

   //...

}

I was puzzled, because the results were always coming back empty. I stepped through the debugger in Android Studio, and found that the contains check is always returning false even when the right ID is known to be present.

Tracing that back with the debugger, I found what I suspect to be the culprit: according to the debugger, List<Long> classIds contains *Integer* objects. What gives? I'm not sure how to debug this any further.

EDIT:

Here's the debugger output the question is based on: enter image description here

EDIT 2:

Here's how the test data is being loaded into the data store, you can see I am correctly passing Long values:

The below method is called by a method which does a similar thing for schools, and then persisted via a method in the test DAO.

public static ClassDto getClassTestData(int classId) {
    ClassDto classDto = new ClassDto();

    switch (classId) {
        case 1:
            classDto.setId(1L);
            classDto.setName("207E - Mrs. Randolph");
            classDto.setTeacher(getTeacherTestData());
            classDto.setStudents(getStudentsTestData());
            return classDto;
        case 2:
            classDto.setId(2L);
            classDto.setName("209W - Mr. Burns");
            classDto.setTeacher(getTeacherTestData());
            return classDto;
        case 3:
            classDto.setId(3L);
            classDto.setName("249E - Mr. Sorola");
            classDto.setTeacher(getTeacherTestData());
            return classDto;
        default:
            return null;
    }
}

EDIT 3:

Here is the DAO where the school information is being persisted/retrieved from. The problem is occuring somewhere between the time that the data is inserted and the time it is removed. It goes in with type Long and comes out with Type Int

@Dao
public interface SchoolDao {

    @Query("SELECT * FROM schools")
   List<SchoolDto> getAllSchools();

    @Insert
    void insertSchool(SchoolDto schoolDto);

    }
Bassinator
  • 1,682
  • 3
  • 23
  • 50
  • Does getId() return a `Long`? – Chris Stillwell Apr 13 '18 at 17:31
  • Yes, the code (for the getter) is here: `public Long getId() { return id; }` – Bassinator Apr 13 '18 at 17:33
  • 1
    The tool you are using to perform the query is producing an `Integer` object for the column in question. You need to either use a list of Integers or tell the db access tool that you want the value to be a Long. – DwB Apr 13 '18 at 17:34
  • 1
    Howe are you populating the `List`? Are they `Long` when they go into the list? – Chris Stillwell Apr 13 '18 at 17:34
  • 2
    `classIds` is a parameter, and isn't changed by this method, so the integers are in there when it is passed in. I would guess there is a raw reference to the list somewhere, and something like `list.add(123)` is happening instead of `123L`. – Andy Turner Apr 13 '18 at 17:35
  • `private List classIds;` is the field in a class called `SchoolDto` where these IDs are coming from. – Bassinator Apr 13 '18 at 17:36
  • For clarification, some of these entities are being loaded via Google's Room ORM for Android (with a REAL DAO implementation) - however, some of them are being loaded by a mock DAO implementation (like the above in the question). – Bassinator Apr 13 '18 at 17:38
  • @AndyTurner wouldn't that not compile? – Bassinator Apr 13 '18 at 17:39
  • @Airhead it would if `list` is a raw-typed reference. – Andy Turner Apr 13 '18 at 17:43
  • @Airhead Where does the `classIDs` list come from? May be this is simple a bug of dynamically generated code at runtime? As the generics can't be checked at runtime dynamically generated code could just fill your list with `Integer` objects... – Robert Apr 13 '18 at 17:59
  • @robert, the class ids is intialized with this line `schoolDto.setClassIds(Arrays.asList(1L, 2L, 3L));` and then the schoolDto is popped in the local database. The value is later retrieved from the local database. The ids are retrieved from the `SchoolDto` and then passed as shown above to get the info about the classes the school contains. – Bassinator Apr 13 '18 at 18:03
  • @Robert I see now upon inspection in the debugger, that the ids list being returned in the classDto is contains integers. So we can now identify a slightly earlier point where the problem is detected. This still begs the question, why? I am investigating now. – Bassinator Apr 13 '18 at 18:06
  • See my 3rd edit edit above... – Bassinator Apr 13 '18 at 18:09
  • Post the ClassDto class, you sure you declared the field as Long? an Long assignment at a an int will be a int – Marcos Vasconcelos Apr 13 '18 at 18:20
  • @Marcos Here's the relevant snippet `@Entity(tableName = "classes") public class ClassDto { @PrimaryKey private Long id;` – Bassinator Apr 13 '18 at 18:21
  • @Airhead have you tried querying the id column in sql? I think there is an implicit cast here. – Full Array Apr 13 '18 at 18:27
  • Ohh. i got, the List of the argument is of integers – Marcos Vasconcelos Apr 13 '18 at 18:46
  • it may be just type erasure and optimization somehow – Marcos Vasconcelos Apr 13 '18 at 18:48
  • Change ur test to add a value that is Long.MAX_VALUE – Marcos Vasconcelos Apr 13 '18 at 18:49
  • I have solved the issue - see my answer for explanation. – Bassinator Apr 13 '18 at 18:49

4 Answers4

1

Wow, what a nightmare. I have found the culprit.

I had created a TypeConverter to turn a List<Integer> to into a string (and back) so that it can be stored in a single column in the DB in room without having to modify the existing DTOs. However, when I switched over to using Long types as IDs, I failed to convert a single generic argument below in the converter; look carefully at the following code:

public class IdsListConverter {

    @TypeConverter
    public List<Long> idsFromString(String value) {
        Gson gson = new Gson();
        if (value == null || value.isEmpty()) {
            return null;
        } else {
            Type resultType = new TypeToken<List<Integer>>(){}.getType();
            return gson.fromJson(value, resultType);
        }
    }

    @TypeConverter
    public String idsToString(List<Long> ids) {
        if (ids == null) {
            return null;
        } else {
            Gson gson = new Gson();
            return gson.toJson(ids);
        }
    }

}
Bassinator
  • 1,682
  • 3
  • 23
  • 50
  • "look carefully" Please indicate what you did to fix it. – Andy Turner Apr 13 '18 at 20:02
  • `Type resultType = new TypeToken>(){}.getType();` should have had the generic changed to `List` – Bassinator Apr 13 '18 at 20:31
  • So, state that in the answer, don't leave readers who don't know what they should be "looking carefully" for wondering. Also, note that `gson.getAdapter(new TypeToken>(){}).fromJson(value)` would have been a type-safe option (assuming this is a `com.google.gson.reflect.TypeToken`). – Andy Turner Apr 13 '18 at 20:43
1

It looks like you found your problem:

Type resultType = new TypeToken<List<Integer>>(){}.getType();
return gson.fromJson(value, resultType);

(in a method returning List<Long>) whereas it should have been:

Type resultType = new TypeToken<List<Long>>(){}.getType();

There is a type-safe way to write this which would have picked up the problem at compile time:

TypeToke<List<Integer>> resultTypeToken = new TypeToken<List<Integer>>() {};
return gson.getAdapter(resultTypeToken).fromJson(value);

This wouldn't have compiled, because the return statement's type is incompatible with the method's return type.

It might be worth looking for other occurrences of fromJson so you can migrate them and see if there are other problems you haven't found yet!

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
0

You look at wrong variables. ClassDao instance is below, you can see "{Long@6495} "1". But the Integer "1" you spread is the element of ClassIds which is omitted in your code. You are sure ClassIds is List(), when adding element, you should do classIds.add(new Long(1)).

Bejond
  • 1,188
  • 1
  • 11
  • 18
  • I intialize the list with the following line before sticking it in the local DB `schoolDto.setClassIds(Arrays.asList(1L, 2L, 3L));` – Bassinator Apr 13 '18 at 18:01
  • When you save your classids to database, can you find out the data type in database? because there's no `long` type in sqlite – Bejond Apr 13 '18 at 18:14
  • It's done with the Room ORM, so I would assume it handles that? – Bassinator Apr 13 '18 at 18:15
  • From https://stackoverflow.com/a/12584590/3908814 we know sqlite use integer to save long. I suggest you try to initlize your classIds with strong convertion to long when fetching from database. – Bejond Apr 13 '18 at 18:18
  • Do you mean manually cast from int to long on retrieving from the database? – Bassinator Apr 13 '18 at 18:19
  • Could the issue be coming from using `Long` instead of `long`? Autoboxing maybe not handled correctly by Room? – Bassinator Apr 13 '18 at 18:20
  • yes, cast integer to long. Then create Long object. – Bejond Apr 13 '18 at 18:23
  • In your Edit 2, why parameter classId is `int`. Please update. – Bejond Apr 13 '18 at 18:27
  • It's a int because you can't use Long in a switch statement; it's just a for-loop index variable. It's value isn't used in insertion. – Bassinator Apr 13 '18 at 18:28
  • Sure I missed. And so you have some code to cast your classId to `int` – Bejond Apr 13 '18 at 18:30
-1

For future reference, this list of casting rules will help you. In essence, I believe there is/was an implicit casting conflict.

byte –> short –> int –> long –> float –> double
Full Array
  • 769
  • 7
  • 12