Office Hours — Today, February 6

Tuesday, February 4

Feb 6
8:20 AM
Mark M.
has entered the room
Mark M.
turned on guest access
8:40 AM
Dirk W.
has entered the room
8:45 AM
Dirk W.
Good morning Mark
Mark M.
hello, Dirk! sorry about the delay there
how can I help you today?
Dirk W.
no problem
I am from Germany. So, sorry in advance for any mistakes in my English writings.
I have some problems regarding Room and LiveData. Perhaps you can help me.
Mark M.
I can try!
Dirk W.
The app I am working on is about storing certain items in shelves.
I have a fragment to create or modify an existing item.
in this fragment I use observers to get data of the item to be modified and data of packaging units out of a room database. The itemdata fills several textfields and the data for the packaging units is used to fill a spinner both objects are connected by a ForeignKey.
In general this works fine. But from time to time I face some racing conditions. The observer of the packaging unit is too slow and won't get loaded before the fragment is visually created. In this case the spinner is not filled with data and causes several problems (object is null etc.).
I have some code here. Perhaps you can have a look and tell me, if I am doing something completely wrong or how I can prevent these race conditions.
Mark M.
are you retrieving the data in one @Query or two?
Dirk W.
it's two different querys
View paste
public interface VpeDAO {
    @Query("SELECT * FROM vpe ORDER BY title")
    LiveData<List<VPEObject>> loadAllVpes();
and
View paste
    @Query("SELECT * FROM Items WHERE caseId=:id")
    LiveData<List<ItemObject>> loadItemsByCaseId(String id);
8:50 AM
Mark M.
is caseId a field on VPEObject?
in other words, is your foreign key relationship between the vpe and Items tables, based on that caseId?
Dirk W.
no it's another object that is a parent of itemObject, also connected by a Foreing KEy
View paste (3 more lines)
@Entity(tableName = "items",
        foreignKeys = {
            @ForeignKey(entity = CaseObject.class, parentColumns = "caseId", childColumns = "caseId", onDelete = CASCADE),
            @ForeignKey(entity = VPEObject.class, parentColumns = "vpeId", childColumns = "vpeId", onDelete = SET_NULL),
            @ForeignKey(entity = DurationObject.class, parentColumns = "durationId", childColumns = "durationId", onDelete = SET_NULL)},
        indices = {
            @Index("caseId"),
            @Index("vpeId"),
            @Index("durationId")})
@TypeConverters(com.omniworld.shelfman.tools.DateConverter.class)
public class ItemObject {

    @NonNull
    @PrimaryKey
    private String itemId; //UUID
...
View paste
    private String caseId; //Foreign key zum Schrank
    private String vpeId; //Foreign key zu VPE
it's not based on the caseId but on vpeId
Mark M.
the reason why I ask is because the most reliable way to get atomic results from Room is to use @Relation
Dirk W.
atomic results means, that both objects are loaded at the same time?
Mark M.
you would have a @Query that returned a LiveData<List<VPEAndItems>>, where VPEAndItems is a POJO
yes
VPEAndItems would have an @Embedded VPEObject and use @Relation for the List<ItemObject>
Dirk W.
ok, understood
Mark M.
however, I am not certain if you can construct a @Relation for your situation -- that is a more complex data structure than I have used with Room
Dirk W.
do you think this is the only way or the best way to solve this?
Mark M.
probably it is not the only way
8:55 AM
Dirk W.
I was thinking about embedding the VPEObject but through it away bacause of the complexity of the structure
throwed not through
Mark M.
(FWIW, "threw" is English past tense for "throw")
(English is weird)
Dirk W.
thanks :-))
Mark M.
another way to approach the problem is to try to handle it in the viewmodel
in the end, ideally IMHO, the viewmodel is emitting the VPEObject and its ItemObjects at the same time
the proposed @Relation approach would do that in the Room DAO
if you have a repository layer between the viewmodel and the DAO, it could try to combine the results
or, if you do not, the viewmodel could try to combine the results
Dirk W.
yes, I have
in the viewModel:
View paste
    public LiveData<ItemObject> getItemObj(String itemId) {
        Timber.d("XX -- getItemObj() itemId %s", itemId);
        return repository.getItemObj(itemId);
    }
    public LiveData<List<DurationObject>> getDurationList() {
        Timber.d("XX -- getDurationList()");
        return repository.getDurationList();
    }
oops, wrong method
View paste
    public LiveData<List<VPEObject>> getVpeList() {
        Timber.d("XX -- getVpeList()");
        return repository.getVpeList();
    }
Mark M.
if this were RxJava, we would use a zip() operator to combine the results of the two queries, so downstream consumers would get the combined results when both results were ready
Dirk W.
and in repository:
View paste
    public LiveData<List<VPEObject>> getVpeList() {
        Timber.d("XX -- ShelfRepository.getVpeList() ");
        return db.vpeDao().loadAllVpes();
    }
    public LiveData<ItemObject> getItemObj(String itemId) {
        Timber.d("XX -- ShelfRepository.getItemObj() itemId %s", itemId);
        return db.itemDao().loadItemByItemId(itemId);
    }
9:00 AM
Dirk W.
I am pretty new to Android programming and didn't 'dare' to look into RXJava
Mark M.
you are using LiveData directly, and so you would need to find or write a zip() sort of "combiner" that would observe each LiveData and only emit something when both supplied a result
it is possible that somebody has already written a zip() for LiveData -- I had a discussion in this very chat room a year or two ago with somebody who had a similar problem to yours
he wound up writing a zip() method himself
Dirk W.
I am not sure how I can combine both DAO calls in the respository
Mark M.
you would wind up using a MutableLiveData or MediatorLiveData
Dirk W.
ok. perhaps I will look into your archive
Mark M.
I can understand the fear of RxJava
Dirk W.
:-)
Mark M.
in the long term, Kotlin and coroutines will be simpler for you to adopt, most likely
personally, I would be going one of those routes, so that my fragment would get a "view state" containing all the data to be rendered
however, it is possible that you could try to fix things in the fragment to be more friendly to data arriving at different times
Dirk W.
Okay, I try to do that in the first step.
My problem is, that the spinner should be filled in the fragment
with the vpe (packaging unit)
before the fragment is shown
Mark M.
um, that does not sound good
you really do not have that kind of control over the timing
Dirk W.
no, not really
9:05 AM
Mark M.
the typical approach is to have a "loading state" that gets displayed while you obtain the data, such as a ProgressBar
then, replace that with the "real" UI once you are in position to display it
Dirk W.
So, then I should check the loading state in both observers and only show the ui when both loadingstates are true, right?
Mark M.
in your case, yes, if you cannot display anything with just the results of one
that is why I like to combine the data earlier on (DAO/repository/viewmodel), so the fragment does not have to keep track of that
Dirk W.
yes, but that sounds like a good idea
I will also check the combine solution. I am currently not quite sure hot to do it, but will have to read into MediatorLiveData
Mark M.
I cover the older Architecture Components version of it in *Android's Architecture Components*
which you can download or read online as part of your Warescription
Dirk W.
yes I did and read through it, but not very closely looked at Mediator
some work for thi safternoon ;-)
9:10 AM
Dirk W.
View paste
Thank you for your help and thank you very much for all the work you do to help people getting better in programming android.
Really appreciate it.
Mark M.
thanks for the kind words, and for subscribing!
Dirk W.
have a good day
Mark M.
you too!
Dirk W.
has left the room
9:25 AM
Mark M.
turned off guest access

Tuesday, February 4

 

Office Hours

People in this transcript

  • Dirk Weber
  • Mark Murphy

Files in this transcript