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!
|
Mark M. |
how can I help you today?
|
sudokai |
Hello
|
sudokai |
I have been thinking about state management
|
sudokai |
And I don't see how ViewModels really solve the problem
|
sudokai |
The problem with ViewModels is that if you want to reuse them, then you make a generic one
|
sudokai |
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
|
sudokai |
I just don't feel it's the right abstraction
|
sudokai |
What are you thoughts on this?
|
Dec 10 | 8:55 AM |
Mark M. |
on the whole, I don't worry about it too much
|
Mark M. |
there are multiple ways of accomplishing viewmodel code reuse
|
Mark M. |
sometimes, I use inheritance
|
Mark M. |
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
|
sudokai |
Any examples of this?
|
Mark M. |
publicly? no
|
Mark M. |
my books are designed to demonstrate API use
|
Mark M. |
and so their examples do not get very complex
|
sudokai |
Okay, how do you "compose" viewmodels?
|
sudokai |
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
|
Mark M. |
that hardware has its own OS and that OS needs to get updated from time to time
|
Mark M. |
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
|
Mark M. |
the key is that we need to do this update logic in a few places
|
Dec 10 | 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
|
sudokai |
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)
|
Mark M. |
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
|
Mark M. |
earlier, you complained about "splitting the reactivity between them"
|
Mark M. |
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
|
Mark M. |
Jetpack is strongly steering us towards single-activity apps
|
sudokai |
Yeah, but that sucks for existing apps
|
sudokai |
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
|
Dec 10 | 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?
|
sudokai |
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
|
sudokai |
It doesn't seem like a big improvement over the non-reactive way
|
Dec 10 | 9:10 AM |
sudokai |
Plus you still need to handle onSavedInstanceState etc.
|
sudokai |
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.
|
Mark M. |
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
|
Mark M. |
ViewModel isn't strictly "trying" to do anything -- it is a tool, nothing more
|
Mark M. |
it has strengths and weaknesses
|
Mark M. |
but, after that, it's all in how you apply it
|
Mark M. |
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
|
Mark M. |
perhaps it was, perhaps it wasn't
|
Dec 10 | 9:15 AM |
sudokai |
Right
|
Mark M. |
from my standpoint, a ViewModel is there somewhat for configuration changes, but mostly for testing
|
Mark M. |
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
|
sudokai |
That's why they didn't make sense for me
|
sudokai |
We don't have tests
|
sudokai |
:D
|
Mark M. |
um, eek
|
Mark M. |
in your case, ViewModel can still have some value, but you are missing a chunk of it because you are missing the tests
|
Mark M. |
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
|
Mark M. |
particularly as unit tests, for fast execution and no need for emulators and stuff
|
sudokai |
We don't use coroutines either
|
sudokai |
So viewModelScope is useless for us too
|
Dec 10 | 9:20 AM |
sudokai |
Another question:
|
sudokai |
I'm seeing code being run non-sequentially in the main thread
|
sudokai |
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
|
Mark M. |
similarly, LiveData will use Handler and Runnable for updating observers, when the update comes from postValue() on MutableLiveData
|
sudokai |
I have this
|
sudokai |
View paste
|
sudokai |
passiveLocationListener is assigned a few lines above
|
sudokai |
Then there's another method that removes the location updates
|
sudokai |
And sets passiveLocationListener to null
|
sudokai |
Both run on the main thread
|
sudokai |
So I see the first code in mid execution
|
sudokai |
Then the second function comes in
|
sudokai |
And by the time this code is run
|
sudokai |
View paste
|
sudokai |
passiveLocationListener is null
|
sudokai |
Nullpointerexception
|
sudokai |
BAM
|
Dec 10 | 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}")
|
sudokai |
I put this at the beginning of both functions
|
sudokai |
And it says main
|
sudokai |
In both cases
|
Mark M. |
I'd use thread IDs, not names
|
sudokai |
These are service methods btw
|
sudokai |
Not ordinary POJO methods
|
sudokai |
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)
|
Mark M. |
services are objectws
|
Mark M. |
er, objects
|
Mark M. |
objects do not run on threads
|
Mark M. |
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
|
Dec 10 | 9:30 AM |
sudokai |
Then in the location callback I unregister the listener and set it to null
|
sudokai |
I have multiple listeners btw
|
sudokai |
So one of the listeners gets a location
|
sudokai |
Calls the location callback
|
sudokai |
All the listeners are unregistered
|
sudokai |
But I see the registering function still running
|
sudokai |
When the unregistering happens
|
sudokai |
And I print the thread, both say main
|
sudokai |
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
|
sudokai |
If I Handler.post the unregister call
|
sudokai |
Then it works fine
|
Mark M. |
yes, multithreading is challenging at times
|
sudokai |
Okay
|
sudokai |
Thanks for your time
|
Mark M. |
FWIW, that further suggests that your unregister is happening on a background thread
|
Mark M. |
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
|
sudokai |
Thanks
|
Mark M. |
have a pleasant day!
|
sudokai | has left the room |
Mark M. | turned off guest access |