Office Hours — Today, December 10

Tuesday, December 8

Mark M.
has entered the room
Mark M.
turned on guest access
Dec 10
8:50 AM
sudokai
has entered the room
Mark M.
hey, hello, sudokai!
how can I help you today?
sudokai
Hello
I have been thinking about state management
And I don't see how ViewModels really solve the problem
The problem with ViewModels is that if you want to reuse them, then you make a generic one
And in your fragment you will end up with several viewmodels
Mark M.
"if you want to reuse them" is a big "if"
sudokai
Splitting the reactivity across them
I just don't feel it's the right abstraction
What are you thoughts on this?
8:55 AM
Mark M.
on the whole, I don't worry about it too much
there are multiple ways of accomplishing viewmodel code reuse
sometimes, I use inheritance
sometimes, I sub-divide the viewmodels by role, for more of a composition feel
sudokai
It's almost like a "service", but not persistent
Mark M.
sometimes, the answer is to get the code out of the viewmodel, pushing into a repository, or perhaps into use cases
sudokai
> sometimes, I sub-divide the viewmodels by role, for more of a composition feel
Any examples of this?
Mark M.
publicly? no
my books are designed to demonstrate API use
and so their examples do not get very complex
sudokai
Okay, how do you "compose" viewmodels?
I don't get it
Mark M.
well, let me give you an example from my current customer, with a lot of the details intentionally "fuzzed", because this isn't public
sudokai
Okay
Mark M.
the customer's app connects to a propriety piece of hardware
that hardware has its own OS and that OS needs to get updated from time to time
so, the app's UI, in a few places, needs to coordinate with the hardware and the user to do an update, such as when your Android phone asks if it is a good time to update
the key is that we need to do this update logic in a few places
9:00 AM
Mark M.
and, we need to check for the updates on "ordinary" screens, ones whose primary function is not related to updates
sudokai
Okay
Sounds really interesting
Mark M.
so, we have a CheckUpdatesViewModel, whose mission is to work with the appropriate repositories and services to check for available updates and their type (nothing, available, required)
and each of the fragments that needs to check for updates has a CheckUpdatesViewModel, in addition to whatever viewmodel it needs for its own purposes
earlier, you complained about "splitting the reactivity between them"
in this case, that is happening, and it is intentional, as "check for updates" is independent of the real business logic for the screen
sudokai
Is this app using a single activity + fragments?
Mark M.
not presently -- it's a mish-mash of activities and fragments
sudokai
Another issue I have is that I can't use the same instance of the viewmodel across different activities
Mark M.
correct, and that's pretty much by design
Jetpack is strongly steering us towards single-activity apps
sudokai
Yeah, but that sucks for existing apps
We have a mix and match too
Mark M.
so, in my customer's app, we have been putting newer screens into fragments, with a thin activity wrapper, with an eye towards integrating Jetpack Navigation next year, and moving more aggressively to a single-activity setup
9:05 AM
Mark M.
note that sharing viewmodel instances introduces its own set of challenges (ensuring that one screen doesn't negatively impact another screen), so while it's a useful tool, it is not appropriate in all cases
sudokai
But that's exactly why you share them right?
So that the reactivity gets propagated everywhere
Mark M.
you share them for *positive* reasons; you need to be careful not to introduce *negative* consequences
sudokai
I have this big fragment with several view models and everything seems so scattered
It doesn't seem like a big improvement over the non-reactive way
9:10 AM
sudokai
Plus you still need to handle onSavedInstanceState etc.
Because there's no automatic state persistence
Mark M.
I can't comment specifically on your case. I don't think I have ever had more than two viewmodels per activity or fragment.
in terms of onSaveInstanceState(), there is viewmodel integration for that, via SavedStateHandle
sudokai
Okay, didn't know that one
Mark M.
it's relatively new -- I think it went stable earlier this year
sudokai
The thing is, it feels to me that ViewModel is trying to be a bit of everything, but it doesn't quite fit
Mark M.
I updated a couple of examples in *Elements of Android Jetpack* to use it, though I think I have plans to write a more realistic one in 2021
ViewModel isn't strictly "trying" to do anything -- it is a tool, nothing more
it has strengths and weaknesses
but, after that, it's all in how you apply it
so, going back to "several view models" -- you need to determine why you elected to divide them up that way and was that the best choice
perhaps it was, perhaps it wasn't
9:15 AM
sudokai
Right
Mark M.
from my standpoint, a ViewModel is there somewhat for configuration changes, but mostly for testing
the more you can push out of activities and fragments and into things like viewmodels, the easier it is (IMHO) to write tests
sudokai
That makes sense
That's why they didn't make sense for me
We don't have tests
:D
Mark M.
um, eek
in your case, ViewModel can still have some value, but you are missing a chunk of it because you are missing the tests
a few of the Jetpack decisions, such as an increased push towards dependency inversion (such as Hilt), is with an eye towards making it easier to write more thorough tests
particularly as unit tests, for fast execution and no need for emulators and stuff
sudokai
We don't use coroutines either
So viewModelScope is useless for us too
9:20 AM
sudokai
Another question:
I'm seeing code being run non-sequentially in the main thread
How is that possible???
Mark M.
well, things like RxJava's AndroidSchedulers.mainThread(), or coroutines' Dispatchers.Main, effectively use Handler and Runnable under the covers, so that stuff will seem non-sequential
similarly, LiveData will use Handler and Runnable for updating observers, when the update comes from postValue() on MutableLiveData
sudokai
I have this
View paste
if (locationManager.isProviderEnabled(LocationManager.PASSIVE_PROVIDER)) {
    locationManager.requestLocationUpdates(
        LocationManager.PASSIVE_PROVIDER,
        1000,
        0f,
        passiveLocationListener!!
    )
}
passiveLocationListener is assigned a few lines above
Then there's another method that removes the location updates
And sets passiveLocationListener to null
Both run on the main thread
So I see the first code in mid execution
Then the second function comes in
And by the time this code is run
View paste
locationManager.requestLocationUpdates(
        LocationManager.PASSIVE_PROVIDER,
        1000,
        0f,
        passiveLocationListener!!
    )
passiveLocationListener is null
Nullpointerexception
BAM
9:25 AM
Mark M.
then, by definition, that second function did not run on the same thread that your original code block did
sudokai
Log.d("sudokai", "${Thread.currentThread().name}")
I put this at the beginning of both functions
And it says main
In both cases
Mark M.
I'd use thread IDs, not names
sudokai
These are service methods btw
Not ordinary POJO methods
Okay, I'll try to check the IDs then
Mark M.
your original concern is sound: a thread cannot be doing two things at once
sudokai
But services run on the main thread, or so I have read
Mark M.
and the points where we switch what is happening are usually pretty visible (e.g., RxJava subscriptions, suspend functions with coroutines)
services are objectws
er, objects
objects do not run on threads
functions run on threads
sudokai
It's mind baffling
Mark M.
certain lifecycle methods/functions on Service, such as onCreate(), are called on the main application thread by the framework
sudokai
I'll check the IDs
Mark M.
but there are only 4 of those functions -- everything else is added by subclasses, and there, it's up to what those subclasses are doing
sudokai
So basically I register my listener to get a fix
9:30 AM
sudokai
Then in the location callback I unregister the listener and set it to null
I have multiple listeners btw
So one of the listeners gets a location
Calls the location callback
All the listeners are unregistered
But I see the registering function still running
When the unregistering happens
And I print the thread, both say main
Anyway, I'll check the IDs
Mark M.
I'm uncertain if names have to be unique -- IDs definitely have to be unique
sudokai
Thanks
Mark M.
and, I need to "switch gears" and turn my attention to the aforementioned customer of mine
sudokai
It's very weird
If I Handler.post the unregister call
Then it works fine
Mark M.
yes, multithreading is challenging at times
sudokai
Okay
Thanks for your time
Mark M.
FWIW, that further suggests that your unregister is happening on a background thread
anyway, that's a wrap for today's chat
sudokai
Okay
Mark M.
next one is Saturday at 4pm US Eastern
sudokai
See you around
Thanks
Mark M.
have a pleasant day!
sudokai
has left the room
Mark M.
turned off guest access

Tuesday, December 8

 

Office Hours

People in this transcript

  • Mark Murphy
  • sudokai