Office Hours Transcript: 2021-11-04
john joined
hello, john!
how can I help you today?
Hello Mark
I need to better understand how viewmodels work
First thing, what is meant by "you can’t pass arguments to viewmodels". I tried this and it works fine, what is wrong?
class MyViewModel(a:A,b:B): ViewModel() {
do stuff
}
======
@Composable
fun MyComposable(viewModel:MyViewModel){
val x = viewModel.getX()
}
===============
class MyClass{
MyComposable(MyViewModel(a,b))
}
you will find that your viewmodel does not work across configuration changes, which is the primary reason in Android for having a viewmodel
Do you mean the data will be reset when there’s a rotation, for example?
correct
I see. Thank you.
for viewmodels to be retained across the configuration change, they have to be managed by the Jetpack ViewModel system
typically, that means that the Jetpack creates your viewmodel instances for you
if you need to have constructor parameters, there are recipes for doing that, using a ViewModelProvider.Factory
or a dependency inversion framework (Dagger/Hilt, Koin, etc.)
but you cannot just create an instance of MyViewModel
yourself as you are in that code
By "you can’t", do you mean "your code won’t work when there is a configuration change"?
yes
Alright
if you are not worrying about configuration changes, then probably you do not need to use the Jetpack ViewModel system at all
Second: Is it true that it is bad to pass a context to a view model. If it is, what should you do when, for example, you need to use a receiver that is triggered with ACTION_TIME_TICK (it has to be registered within the class, it can’t be in a separate one).
Is it true that it is bad to pass a context to a view model
Using the Application
singleton is fine. You can do that via AndroidViewModel
or your dependency inversion framework (if you use one).
you need to use a receiver that is triggered with ACTION_TIME_TICK (it has to be registered within the class, it can’t be in a separate one)
I would be somewhat surprised if a viewmodel were the right place for that, but the Application
singleton would work. You would unregister the receiver in onCleared()
of the ViewModel
.
I need to update a value every minute, and display that value on the screen. Where else would you suggest I put the receiver?
I would not use ACTION_TIME_TICK
at all for that, unless you have some specific reason to do so. I would use ScheduledExecutorService
or some other in-process timing solution.
What’s wrong with ACTION_TIME_TICK?
- you have no control over it
- it requires platform APIs, such as `Context,
- it is difficult to mock in testing as a result of the preceding bullet
you are welcome to use it if you want – but, if I were on your development team, I would want to see arguments for using ACTION_TIME_TICK
, rather that considering it to be the default choice
or, to put it another way, I hear about ACTION_TIME_TICK
approximately once every two years, so I do not think that it is particularly popular as a mechanism
Why is it bad that it requires platform APIs? Only because of testing? (sorry for asking annoying questions…)
testing is a large portion of it, particularly unit testing
you also get into the question that sent you down this path: where do you get the Context
from?
and are you using a good Context
that avoids memory leaks in the particular use case?
I suspect it may even be less code to use ScheduledExecutorService
, though neither are especially complex
Very fair point, I never understood why you needed a context to use those kinds of receivers.
it stems from some API design choices made ~15 years ago
a lot of modern Android app development is working around the design choices that did not hold up well to "the test of time"
I see!
You can do that via AndroidViewModel or your dependency inversion framework (if you use one).
do you mean use AndroidViewModel OR ViewModel + DI?
yes; you do not need AndroidViewModel
if you are using the popular DI frameworks
I see! Thank you for making this clear.
And I guess it is the same when I want to use a Room database? I can use ViewModel + DI instead of AndroidViewModel?
your viewmodel would not necessarily need a Context
to use Room
ideally, your Room database is managed by some sort of singleton, and that needs a Context
if you are not using DI, you may wind up initializing that singleton from your Application
object – if you want to lazy-initialize Room, then perhaps your viewmodel would need a Context
but, seriously, use DI – while Hilt makes Dagger a bit easier, Koin is really quite simple and works fine for smaller projects
I need to learn more about that. If I were to use AndroidViewModel on the other hand, I need to pass an application. I see people pass "this" from their activities, but isn’t that a Context?
If I were to use AndroidViewModel on the other hand, I need to pass an application
that is handled by the Jetpack ViewModel system for you, when it instantiates your viewmodel
if you are implementing your own ViewModelProvider.Factory
and therefore need to instantiate the viewmodel yourself in the factory, then you should pass in an Application
to avoid the memory leak
besides, AndroidViewModel
takes an Application
, not just a general Context
Yes, so where do I get the Application from? (when I look at code samples, they use "this", so I’m confused)
call getApplication()
on some other Context
, such as an Activity
Like "this.getApplication()"?
you would not need the this.
part, but yes
BTW, my apologies: Context
has getApplicationContext()
I see. Thank you.
About DI, I’m a Python so I never used it before. After researching it, I was under the impression that for simple projects you didn’t really need. Isn’t it simpler to do it "by hand"? Do you recommend to always use no matter what?
Isn’t it simpler to do it "by hand"?
That depends entirely on what "it" is! 😁
for an Android app project that is more than just some simple activity, I would use Koin
for an Android app project with 2+ team members, I would use Dagger/Hilt
that is due to some Android-specific considerations, like Context
and configuration changes and stuff
so, for example, a have not used Koin for various command-line Kotlin utilities that I have written
for an Android app project with 2+ team members, I would use Dagger/Hilt
Interesting comment. Would you mind explaining why the number of team members matter?
there are two major types of dependency inversion: dependency injection and "the service locator pattern"
to keep the story simple, dependency injection does most of its work at compile time via code generation
while service locators are purely runtime solutions
the downside of a purely runtime solution, in strongly-typed languages like Java and Kotlin, is that you lose type safety
with dependency injection, you maintain type safety, at the cost (at least in the Java/Kotlin world) of greater setup complexity
with one developer, and a project sized for one developer, the loss of type safety for "injected" values is not a big deal – that developer probably will not make mistakes
once you start getting into bigger projects and having to involve multiple people, it becomes more likely that you will encounter bugs that type safety would have caught at compile time, but because you are using a service locator, you don’t find out until runtime and you crash
personally, my dividing line for when the overhead of Dagger/Hilt is worthwhile would be a project with 2+ maintainers, but that’s just me – there are projects out there that use Koin and other service locators that have multiple developers
but, for my own solo stuff, Koin suffices and is very simple to set up
Thank you for educating me on this. Very interesting.
One last question.
if you are not worrying about configuration changes
When people talk configuration changes, the only example I’ve ever heard is "rotation". But I guess you can forbid the app to rotate. What other examples might there be?
there are lots and lots of configuration changes:
- changing locale/language
- toggling dark mode
- connecting/disconnecting a keyboard
- swapping SIMs (for devices with externally-available SIM slots)
- on Android 12, changing the wallpaper
- etc.
https://developer.android.com/guide/topics/manifest/activity-element#config has a table listing many (but not all) of them
I see. So was
if you are not worrying about configuration changes
kind of semi-sarcastic comment? (which is totally fine 😄)
you can opt out of most of the configuration changes’ default behavior of destroying and recreating your activities and fragments – the manifest attribute from my previous link covers that
and, in Jetpack Compose development, that approach has been gaining momentum
outright ignoring configuration changes – neither opting out of them nor using ViewModel
correctly – is bad
however, a lot of apps that do not support both portrait and landscape ignore configuration changes
so, for example, Android 12’s change-the-wallpaper configuration change is one that you cannot opt out of (https://commonsware.com/blog/2021/10/31/android-12-wallpaper-changes-recreate-activities.html), and that causes problems for a bunch of apps
(see https://www.xda-developers.com/android-12-game-crashing-automated-wallpapers/ for more on the wallpaper problems)
the whole wallpaper thing happens to have come to everyone’s attention (including mine) this week
Interesting! I shall look into that.
BTW, what’s the reason we don’t just only use "remember" in Jetpack Compose?
Is it mostly to "separate business logic"?
remember()
only works inside a composable
if you have state that you are updating from outside of a composable, such as from Room, you will need some other mechanism than literally remember()
Theoritically you can do almost anything inside the composable, can’t you? The only time I had an issue was when I tried to select a file (registerForActivityResult)
that starts to get more into "separation of concerns"
True. Thank you so much for time Mark.
you’re welcome!
john left