Office Hours — Today, May 14

Tuesday, May 12

May 14
8:20 AM
Mark M.
has entered the room
8:25 AM
Mark M.
turned on guest access
8:30 AM
Kai H.
has entered the room
Kai H.
Hi Mark
terence
has entered the room
Mark M.
hello, Kai!
terence
morning mark
Mark M.
and hello terence!
Kai H.
hi
Mark M.
Kai, I think you got in first, so... how can I help you today?
Kai H.
I'd give Terence precedence if he has a question
I feel like I get a lot of time as I am present in most office hours :D
Mark M.
OK, sounds fine
terence
thanks
Mark M.
Terence: hi again! how can I help you today?
terence
I’m trying to understand how caches are typically used in modern apps. Suppose I have a list of items retrieved from a server. I also have a cache mechanism and progress indicator. When is the cached utilized? Is it before items are retrieved from the server? If a progress indicator is usually displayed prior to a response from a server is it still displayed?
Mark M.
are you referring to an on-disk cache or an in-memory cache? or both?
terence
would the answer be different? on-disk
Mark M.
an in-memory cache is usually purely a cache
8:35 AM
Mark M.
once you start persisting data to disk, the debate starts to creep in: is this really a cache, or is this more of a primary data source?
if you truly mean a cache, and that the server is the system of record at all times, then the next question is: does your app think that the cache is reasonably up to date at the time you are trying to show this list?
it also depends a lot on the nature of the on-disk cache -- caching OkHttp/Retrofit responses is different than having your own Room tables, for example
the simplest caches are transparent to your app code, such as the OkHttp/Retrofit response caching
terence
by cache I mean storage used to restore an app if it is destroyed involuntarily so I was thinking sharedpreferences or a room database
Mark M.
OK
if you think the cache is likely to be fairly up to date, a common approach is to show the cached results, while doing some background work to ensure the cache is updated (and also update the UI with any changes)
the algorithm for "fairly up to date" will depend partly on the age of the cached data and partly on the frequency of data changes on the server
if the user's data on the server doesn't change very often, the cache is more useful
8:40 AM
Mark M.
in terms of a progress indicator, I'd consider one during the disk/database I/O
8:40 AM
terence
so does the user see cache and then a progressbar followed by network data?
Mark M.
that is up to your UI designers
I would not use a blocking progress indicator, like a dialog, if the cached data is useful
but, if you want some sort of indicator that shows that a sync is ongoing, you might find a way to do that
terence
why would you place the progress indicator during the database io, isnt fetching from the network usually a longer task
Mark M.
yes, but there is nothing to show the user before the database I/O completes
if you are very confident that the database I/O will be quick, you could skip the progress indicator
however, phones vary quite a bit in performance, and I do not know how complex your database I/O would be
terence
thanks, i will ask a follow up but let kai go
Mark M.
OK, sounds good -- I'll come back to you in a bit
Kai: your turn!
Kai H.
I have implemented a list with RecyclerView and an Adapter and the such, following your example from "Jetpack" mostly.
I show an InputDialog if one of the list items is clicked.
And wondered where to place the Dialog and how to get and store the result
Mark M.
I am uncertain what you mean by "where to place the Dialog"
Kai H.
The way I do it right now is that I initialize my Adapter with a callback and then start the Dialog from there.
Mark M.
ah, OK
8:45 AM
Mark M.
is this Dialog managed by a DialogFragment?
Kai H.
The callback is from the Fragment that holds the RecyclerView and Adapter.
Yes, it's a DialogFragment.
Sorry, I am actually wrong :D I just looked it up again
Mark M.
that's too bad, because showing the dialog from that callback seems reasonable, based on your description :-)
Kai H.
I start the DalogFragment from another Fragment (the one that is shown prior to starting the Dialog).
Mark M.
OK, that sounded like what you wrote before, though
Kai H.
And it started from the Adapter via a Callback that I passed into the Adapter constructor
I thought I actually have the Dialog _inside_ my Adapter, which would be rather strange :D
Mark M.
if you are showing the DialogFragment from the hosting Fragment, via a callback supplied to the RecyclerView.Adapter, that sounds fine
I would not have the Adapter itself show the DialogFragment, but having it invoke a callback on the click(?) event is fine
Kai H.
It feels a bit... messy for some reason. All over the place
Yes, it's a click event. I also pass in a Listener for a drag event, to be able to drag list items via a drag handler.
8:50 AM
Mark M.
well, only the Adapter and the ViewHolder know about the UI events on the RecyclerView items, so they have to be involved somehow
at the same time, only the Fragment knows what is to be done when the events occur
so, you need to ripple the UI events up to the Fragment by one means or another
Kai H.
I also pass in the ViewModel of the parent Fragment, because there are some fields I need from it.
Ok.
Mark M.
a callback is a reasonable approach
my current client project uses RxJava PublishSubject objects, which isn't my preferred approach but works
Kai H.
Do you prefer Callbacks?
Mark M.
a Kotlin project could use a BroadcastChannel from coroutines
for a Java project, and in this situation, callbacks are fine
a Fragment and a RecyclerView are tightly coupled by their very nature
contrast that with a Fragment and its ViewModel, which may have distinctly different lifecycles
but, one way or another, I tend to put navigational thinks like FragmentTransactions, showing DialogFragments, startActivity(), on the activities/fragments, not on UI elements like a RecyclerView
(er, navigational *things*)
Kai H.
Yes, that feels better. I also do that (it seems ;-) ).
Mark M.
let me take another question from Terence, and I'll come back to you in a bit
Terence: back to you! do you have another question?
8:55 AM
terence
Suppose I want my list of results to be up to date so I fetch from the network during onResume(). Is there a way to prevent making the network request on rotation and continue to request new data during onResume()?
Mark M.
ideally, in modern Android architectures, your network I/O is being managed by a ViewModel, so it survives configuration changes and knows when that I/O is needed
whether your ViewModel *does* the I/O, or if that is handled by a singleton repository, or by a data source object managed by a singleton repository, is a separate matter
terence
I am using a presenter
Mark M.
one way or another, you want the I/O to be managed by something with an appropriate lifecycle, and one that is not destroyed/recreated due to a configuration change
in the Jetpack, that would be a ViewModel
other frameworks hopefully have equivalent options
terence
I am using a presenter as part of MVP; typically I make my io requests in my presenter that is heavily tied to the activity lifecycle
Mark M.
I would get the actual I/O out of there, perhaps into a repository object that survives configuration changes
your presenter would then ask the repository object for the data, and the repository object would know the state of the cache, whether network I/O is needed in this case, etc.
9:00 AM
terence
should this repository be a singleton to outlive the activity lifecylce?
Mark M.
yes, ideally managed by some sort of dependency inversion framework (Dagger, Koin, etc.)
so that in testing you can swap in a mock or fake repository
terence
im confused how this would work, do I query the repository in onResume()?
Mark M.
I cannot really answer that, as I do not know enough of your code
I show the use of repositories in a few spots in *Elements of Android Jetpack*
and *Exploring Android* sets up an app that uses repositories as well
terence
yes, but it uses viewmodels and i am not using it
Mark M.
sorry, but I have not worked on an MVP Android project and cannot really comment on how that architecture works with respect to lifecycle methods
this is one of the reasons why I try to focus more on APIs than architecture in my books
however, it does limit my ability to help with your sort of question, and for that I apologize
let me take another question from Kai, and I will return to you shortly
9:05 AM
terence
sure
Mark M.
Kai: back to you! do you have another question?
Kai H.
Thanks
Do you prefer to group files by function or by type? Like "all files concerning one piece of functionality go there" vs "all Fragments go there, all ViewModels here, all..."
Mark M.
if by "function" you mean "subject matter", I tend to go that route
depending on the app, that has a tendency to map to logical screens
Kai H.
function = funcionality, some aspect of the app like "show a list of items" or "create a new item"
Mark M.
yeah, OK, then that's what I tend to see
you can see a bit of that in the to-do app in *Exploring Android*
Kai H.
Doesn't that create problems if/when you have a hierarchy of some sort? Say you have a base fragment with some functionality that your other fragments inherit from?
hierarchy = inheritance hierarchy
9:10 AM
Mark M.
for those common elements, I've seen a few patterns, from "dump it in the top-level package" to "have a utility or UI package"
the original reason for "package by functionality" stemmed from Java's package-private visibility option, as closely-coupled items (fragment and a viewmodel, for example) could rely on package-private stuff that other packages could not access
Kotlin doesn't really have package-private, so that particular bit of rationale falls away
Kai H.
The app I maintain goes very heavy on the "group by type", but I often feel it would be nicer to have everything regarding a feature in one place instead of having it spread out into the respective packages.
Mark M.
I agree, but wholesale refactorings can be a pain
Kai H.
I guess so. That should be easier in this case.
There seems to be no real answer to this one besides: It depends.
Mark M.
sorry, I meant switching organization schemes from what you have to what you want
I'
I'd phrase it as "there is no canonical right answer"
9:15 AM
Kai H.
Oh, ok. I thought about switchting from Loaders to ViewModels for example, that could also be easier if all the components that need to be touched are in one place already.
On the other hand, it might make it more difficult, as it might mean "refactor everything at once or nothing at all"
Instead of just refactoring one piece and then another
Mark M.
it's tough for me to comment, since I don't know your codebase
let me switch back to Terence, and I'll try to return to you once more before the chat is over
Terence: do you have another question?
terence
if I used a viewmodel like you suggested, how does it know to serve a result from a database versus a server? Is it correlated to its view's lifecycle>
Mark M.
well, on its own, it might not -- it depends on how much work you're trying to cram into the viewmodel
if you are employing repositories, they definitely need to know the state of their caches, the state of outstanding network I/O, etc.
in that case, the viewmodel is mostly a container for a chunk of results and coordinator for requests to the repositories
terence
so who fetches from the repo and makes that decision?
Mark M.
the viewmodel asks the repo for the data, but the viewmodel should neither know nor care how that data is obtained
whether the repository is using an in-memory cache, on-disk cache, network call, or random number generator is outside the viewmodel's area of responsibility
key to that is having a reactive API from the repository, so the repository can deliver results asynchronously, and perhaps repeatedly (e.g., cache results vs. final network call results)
9:20 AM
Mark M.
so, for example, you mentioned Room
one approach to using Room for an on-disk cache is to have the repository return the results of a call to a Room DAO, where those results are a LiveData or Observable or Flow
Room will then not only do its query when asked, but it will deliver fresh updates when the data changes
the repo can then kick off the network I/O, which updates Room
Room, in turn, emits fresh query results via the LiveData/Observable/Flow
all the viewmodel needs to is tell the repository "give me my data"
terence
what book and chapter contains an example using both room and networked results? I read a chapter that just described using room with livedata, no network results
Mark M.
IIRC, the Weather sample in the *Elements of Kotlin Coroutines* appendix uses both Room and Retrofit
terence
I still dont understand who makes a decision to get database or network results
Mark M.
the repository would, based on the state of the cache
terence
and how that decision is made i mean
Mark M.
I can't answer that, as it depends a *lot* on your data model and your server
terence
in your example, we have a list of data persisted to room. when the cache is looked at, what criteria would trigger a room fetch versus a new network request?
Mark M.
for example, let's suppose that we are writing an app that is displaying financial data, such as stock prices or currency conversion rates
9:25 AM
Mark M.
in the case of financial data, either it changes very rapidly (when markets are open) or not at all (when markets are closed)
so, the repository can know those business rules
when markets are closed, if the cache is up to date with respect to the last closing time, it can return the cached results without bothering with a server call
terence
oh, I see, so you would have logic in your repository to decide whether to use the network DAO or room DAO
Mark M.
when markets are open, it might return the cached data but also make the network call
right
and the details of that logic depend on your particular app and its data sources, so there is no universal solution
terence
View paste
before you go, I want to convert Class<?> srcType = value.getClass() to kotlin.  I tried autoconverter and got srcType: Class<*> = value!!.javaClass, but kotlin errors saying inferred type R is not a subtype of Any.  Any idea?
Mark M.
um, off the top of my head, other than the !!, what you have seems like it should have been OK
that's the sort of thing I'd have to poke around in the IDE for myself
terence
ok
thanks mark!
bye
Mark M.
see you later!
Kai: do you have another (quick) question?
Kai H.
Not quick enough I guess ;-)
I will have to wait for saturday
Mark M.
yeah, there's always another chat! :-)
Kai H.
Are you immortal?
9:30 AM
Mark M.
not that I am aware of
Kai H.
;-)
Have a good time
Mark M.
you too!
the next chat is Thursday at 4pm US Eastern
have a pleasant day, and stay healthy!
Kai H.
has left the room
terence
has left the room
Mark M.
turned off guest access

Tuesday, May 12

 

Office Hours

People in this transcript

  • Kai Hatje
  • Mark Murphy
  • terence