Oct 6 | 3:55 PM |
Mark M. | has entered the room |
Mark M. | turned on guest access |
Oct 6 | 4:00 PM |
Jan | has entered the room |
Mark M. |
hello, Jan!
|
Mark M. |
how can I help you today?
|
Jan |
Hello, Mark! I would like to ask why should we use UUID as a primary key in the Room instead of int (autogenerated)?
|
Mark M. |
"should" is a strong term -- I happen to use UUIDs, but that is not a requirement
|
Aaron | has entered the room |
Mark M. |
I prefer to know my primary key before the data is inserted into the database
|
Oct 6 | 4:05 PM |
Jan |
I understand. But what are the benefits?
|
Mark M. |
well, one benefit is knowing the primary key before the data is inserted into the database
|
Mark M. |
that makes it easier to set up relations ahead of time, for example
|
Mark M. |
plus, it is more difficult to have immutable objects as your entities if you use an autogenerated primary key
|
Mark M. |
you are absolutely welcome to use an autogenerated primary key if you wish
|
Mark M. |
(BTW, hello Aaron -- I will be with you shortly!)
|
Aaron |
hello, and np
|
Jan |
Do you think there is any disadvantage using UUID?
|
Mark M. |
I have not done performance testing, but it is possible that generating a UUID in Java is a bit slower than having SQLite assign an integer primary key
|
Mark M. |
I expect that the difference will be dwarfed by the disk I/O time, though
|
Mark M. |
UUIDs are bigger, both in memory and on disk, which is not great
|
Mark M. |
so there are trade-offs that larger apps may wish to consider
|
Mark M. |
let me take a question from Aaron, and I will be back with you shortly
|
Mark M. |
Aaron: your turn! do you have a question?
|
Jan |
Ok, no problem :)
|
Oct 6 | 4:10 PM |
Aaron |
yes, I am implementing data binding in my app and I have a couple questions about that. first,
|
Aaron |
I am planning to bind to LiveData directly. if I understand right, this eliminates the need for an Observer, correct?
|
Mark M. |
correct
|
Aaron |
ok, great, and the second part of this is,
|
Aaron |
my app is based around one big, many-layers-deep model object that contains all kinds of different variables, so I need to extract the ones I want to bind them to the correct views, and I am trying to determine the best way to do this. your book mentions @BindingConversion as one option. as far as I can tell, the other main option would be to manually split my mega-object LiveData into many smaller LiveDatas, each which contains the data for one view. am I correct in saying this is the primary alternative, and if so, can you say anything about the pros and cons of each?
|
Mark M. |
in the phrase "each which contains the data for one view", what is your definition of "view"?
|
Mark M. |
do you mean widget? do you mean activity/fragment? do you mean something else?
|
Aaron |
widgets, mostly TextEdits
|
Aaron |
sorry
|
Aaron |
TextViews
|
Aaron |
*
|
Mark M. |
having individual LiveData objects per widget seems excessive
|
Oct 6 | 4:15 PM |
Mark M. |
though I have seen people do that
|
Oct 6 | 4:15 PM |
Mark M. |
personally, I prefer to have a "viewstate" object that has the data needed by the layout, and have a LiveData<ViewState> that is used for the data binding
|
Mark M. |
but there are lots of possibilities
|
Mark M. |
basically, I like having atomic changes to my model data and its visual representation -- you'll see that philosophy in the MVI discussions in "Android's Architecture Components"
|
Aaron |
hmm, OK, I need a moment to prepare my follow-up questions, so Jan, if you have more questions, please go ahead
|
Mark M. |
not everything neatly fits that (e.g., exceptions), and so some things might wind up in separate LiveData objects
|
Mark M. |
OK, Jan, back to you! do you have another question?
|
Jan |
Hmmm, I am thinking about using Integer instead of int as a primary key. Is it possible? It will allow me to save null.
|
Mark M. |
yes, and IIRC that is the recommended approach
|
Mark M. |
though usually null is not a value that you would save for the primary key
|
Oct 6 | 4:20 PM |
Mark M. |
foreign key relations, though, might have null indication "no relation"
|
Mark M. |
and null meaning "not yet inserted" is OK in a primary key role
|
Jan |
You're right ... I have a little experience with databases.
|
Jan |
I need to test it more. My app is not in production so no problem.
|
Jan |
Thanks Mark for your answers. I have to go now.
|
Mark M. |
you're welcome!
|
Mark M. |
Aaron: back to you!
|
Jan |
Bye for now.
|
Aaron |
OK, so to make sure I understand, w/r/t the "viewstate", in that case, what you are doing is taking your BigModelObject and extracting out ONLY the data relevant to the layout, and putting all that into a new viewstate object wrapped by a LiveData? Probably by constructing it with a MutableLiveData?
|
Mark M. |
JanL see you next time!
|
Mark M. |
that's one possibility
|
Mark M. |
if the view is only on a piece of the BigModelObject, and just handing the BigModelObject to the layout for data binding would be difficult/inconvenient/icky, then somewhere there is a conversion
|
Jan | has left the room |
Mark M. |
there are a few options for how you do that conversion and data bind it
|
Mark M. |
I am assuming from context that somewhere you have a LiveData<BigModelObject>
|
Mark M. |
am I correct in that assumption?
|
Aaron |
please do summarize those options
|
Aaron |
yes
|
Oct 6 | 4:25 PM |
Mark M. |
one possibility would be Transformations.map()
|
Mark M. |
this takes your LiveData<BigModelObject> and a lambda or Function that can convert BigModelObject into StuffNeededByYourLayout, giving you a LiveData<StuffNeededByYourLayout>
|
Mark M. |
if your data binding has access to that LiveData<StuffNeededByYourLayout>, you can have binding expressions accessing fields or methods of StuffNeededByYourLayout
|
Mark M. |
another possibility is that your activity/fragment observes the LiveData<BigModelObject> and then binds individual things onto your data binding
|
Mark M. |
so, inside of the observe() lambda or Observer, you are calling methods on your data binding for various variables
|
Mark M. |
this is more of pushing the changes into the data binding, rather than having the binding respond to reactive changes
|
Oct 6 | 4:30 PM |
Mark M. |
a variation on that theme is to have the observe() lambda/Observer populate 1+ MutableLiveData objects, which you have previously bound into the data binding
|
Oct 6 | 4:30 PM |
Mark M. |
another variation on that theme is that you have some object that is an MVVM-style viewmodel (not necessarily an Architecture Components ViewModel) that is Observable, and you data bind that into the layout, and your observe() lambda/Observer is updating that viewmodel
|
Mark M. |
the first approach (Transformations.map()) is the simplest, if you can reasonably create a StuffNeededByYourLayout class
|
Mark M. |
there are probably other approaches too that I'm not thinking of right now
|
Oct 6 | 4:35 PM |
Aaron |
OK that's an extremely helpful summary. I think in my particular case today, there is really no distinction between my model object and what is needed by my layout, as I'm not storing any state, just displaying the copypastas and cat pics of the hour from Reddit, so I will probably not bother to construct a separate viewstate object. but, the approach I would like to try in this app is what you identified here: "having the binding respond to reactive changes". Here is where I am getting stuck exactly:
|
Aaron |
(typing)
|
Mark M. |
(waiting with breathless anticipation)
|
Mark M. |
(**gasps**)
|
Mark M. |
(now waiting and breathing again)
|
Aaron |
let me know if I should call the ambulance
|
Mark M. |
(wheeze) no, I'll (cough) be OK
|
Oct 6 | 4:40 PM |
Aaron |
View paste
|
Mark M. |
let's say that your <variable> is a ViewModel named vm
|
Aaron |
ok
|
Mark M. |
and let's say that the ViewModel has a field named stuff that is a LiveData<StuffNeededByYourLayout>
|
Aaron |
ok
|
Mark M. |
and let's say that StuffNeededByYourLayout has a field name title
|
Mark M. |
that you want to bind into a TextView
|
Aaron |
ok
|
Mark M. |
your android:text binding expression would be @{vm.stuff.title}
|
Mark M. |
vm is the variable
|
Mark M. |
stuff is the LiveData in vm
|
Aaron |
D:
|
Aaron |
is it really that simple
|
Aaron |
oy
|
Mark M. |
data binding then realizes that this is a LiveData, so it sets up an Observer for stuff
|
Oct 6 | 4:45 PM |
Mark M. |
yes
|
Oct 6 | 4:45 PM |
Mark M. |
I haven't written about it yet, but I've been using it on a project over the past few months
|
Mark M. |
it's pretty slick
|
Aaron |
I mean... it doesn't work that way in java, right? Or am I wrong about that too
|
Mark M. |
no, Java is more complex than that
|
Mark M. |
even Kotlin is more complex than that
|
Mark M. |
but, it's a logical extension of how they handle Observable fields
|
Mark M. |
suppose vm is the ViewModel, stuff is an Observable<StuffNeededByYourLayout>, and title is a field in StuffNeededByYourLayout
|
Aaron |
yes, that is very good, and I am pleasantly surprised
|
Mark M. |
your binding expression is the same: @{vm.stuff.title}
|
Aaron |
I searched for many examples of Livedata with data binding online, in addition to reading and re-reading your book, and nowhere did I find any examples working like this. I did not even bother to try myself as I just assumed it would not work.
|
Aaron |
but that is great
|
Mark M. |
first, it's pretty new -- I think it debuted with AS 3.1 and the related tools
|
Mark M. |
and Google hasn't really talked about it much
|
Mark M. |
but yes, it's one of those things that I need to write up someday
|
Mark M. |
before I forget, some more notes that I typed up earlier (while waiting on Jan):
|
Mark M. |
the biggest downside that I have run into is that data binding does not seem to like generics -- I have had no luck in declaring a variable of LiveData<Foo>
|
Mark M. |
however, if your variable is something else (e.g., a ViewModel) that happens to hold a LiveData<Foo>, that works quite nicely
|
Aaron |
hmm, ok
|
Mark M. |
that's why, in the examples that I wrote just above, I have the <variable> be a ViewModel
|
Aaron |
got it
|
Oct 6 | 4:50 PM |
Mark M. |
it does not have to be a ViewModel, but it has to have fields/methods that have your LiveData, rather than trying to bind the LiveData in directly
|
Aaron |
OK
|
Mark M. |
alternatively, if you are creating your own LiveData subclasses, those are fine to bind in
|
Mark M. |
it's the angle brackets in the generic declaration that screw up LiveData<Foo>
|
Mark M. |
because we're in XML, and angle brackets have particular meaning there
|
Mark M. |
I forget if I tried LiveData<Foo>, but that's just ugly
|
Aaron |
ok, good tip
|
Aaron |
please continue if you have any other notes (I really wish this chat had a typing indicator), but I have one more question to hopefully address in the time remaining
|
Mark M. |
no, that was it
|
Aaron |
I would like to ask this in a more granular way, but I did not have time to properly research beforehand, so sorry about that,
|
Aaron |
but i am a bit fuzzy on MutableLiveData vs. MediatorLiveData vs. Transformations (particularly the last two)
|
Aaron |
I am not sure exactly where these overlap in purpose and differ
|
Aaron |
if you could provide a high level summary in the next five minutes to point me in the right direction to reserach further, that would be great
|
Mark M. |
Transformations has two static methods: map() and switchMap()
|
Mark M. |
IIRC, if you look at their implementation, you will see that they use a MediatorLiveData under the covers
|
Oct 6 | 4:55 PM |
Mark M. |
from what I can tell, MediatorLiveData is there to do the "heavy lifting" behind those sorts of transformation methods
|
Mark M. |
in my "LiveData Transformations" chapter of "Android's Architecture Components", I work through an example of this
|
Mark M. |
I create a filter() method that works like map() and switchMap(), but instead of converting performs a filter (only emitting objects that match certain criteria provided by a function)
|
Mark M. |
MediatorLiveData can also be used if you have multiple/changing LiveData sources but want a stable LiveData that can be used by a consumer
|
Mark M. |
I work through an example of that in the "LiveData and Data Binding" chapter
|
Mark M. |
in the context of your app, suppose that you had a search screen, with a search field and a RecyclerView of search results
|
Mark M. |
if the user already has done a search, and types in a separate search and submits it, you decide that you want to keep the user on this same screen, rather than creating a new fragment to show the new search results
|
Mark M. |
however, you are already using the LiveData from your previous search request, and it may be inconvenient for you to swap in some other Livedata
|
Mark M. |
so, instead of your fragment using the search result LiveData directly, you have it observe a MediatorLiveData, probably managed by your ViewModel
|
Oct 6 | 5:00 PM |
Aaron |
I see
|
Oct 6 | 5:00 PM |
Mark M. |
when a search is done, the LiveData of search results is attached to the MediatorLiveData, and the old search results are detached
|
Mark M. |
now your fragment has a single MediatorLiveData that it observes all the time
|
Mark M. |
but it still gets the new data sets based on a search
|
Aaron |
ok, thanks for the info, let me not keep you past the hour, I will look into these examples and ask more questions later if needed
|
Mark M. |
so, those are two uses of MediatorLiveData that I know of
|
Mark M. |
OK
|
Mark M. |
the next chat is Tuesday at 9am US Eastern
|
Aaron |
btw, I just noticed a typo on p.171 of architecture book, last paragraph, "that return" -> "that returns"
|
Aaron |
ok great
|
Aaron |
thanks!
|
Mark M. |
OK, I'll get that fixed
|
Aaron |
enjoy the rest of the weekend
|
Mark M. |
BTW, I assume you had no problems getting into the new site, since you're here :-)
|
Aaron |
yes
|
Aaron |
I'll let you know if I have any feedback on it, haven't really looked closely yet
|
Mark M. |
thanks!
|
Aaron |
but it worked well enough to get me here
|
Aaron |
talk later!
|
Aaron | has left the room |
Mark M. | turned off guest access |