Status Update
Comments
si...@gmail.com <si...@gmail.com> #2
il...@google.com <il...@google.com>
ap...@google.com <ap...@google.com> #3
il...@google.com <il...@google.com> #4
si...@gmail.com <si...@gmail.com> #5
Currently the combination of Hilt, SavedStateHandle and Safe args is not possible. The closest I've seen is to drop using safe args and just use magic strings. This also not ideal re dependency inversion as now you have to construct a mock SavedStateHandle for unit tests etc.
class ArticleViewModel @ViewModelInject constructor(
private val repository: ArticleRepository,
@Assisted savedStateHandle: SavedStateHandle
) {
// FIXME: oops, magic "articleId" string
private val articleId: Long = savedStateHandle["articleId"]
?: throw IllegalArgumentException("Article ID required")
}
na...@google.com <na...@google.com> #6
I thought I needed this, but then I realized that I can just create an empty Bundle, store the Fragment's arguments in it as a Bundle (so a Bundle inside a Bundle) and use that to initialize the SavedStateHandle. Then I can just retrieve the arguments from the SavedStateHandle and use the already existing fromBundle
method.
What I like about this is that I get to have immutable init arguments, as long as I store the Fragment's arguments with a key that I would never use for other entries.
With a helper function and a base ViewModel class you can hide this entire process and have ViewModels that expose the arguments object as property/member of the ViewModel itself.
si...@gmail.com <si...@gmail.com> #7
Could you provide an example of how you achieve that?
il...@google.com <il...@google.com> #8
What I wrote requires the ability to provide a Bundle
to initialize the SavedStateHandle
and currently that's not possible with Hilt. You need to wait for this to be fixed:
If you do not use Hilt, then what I wrote becomes straightforward. Rather than passing the Fragment's arguments directly, you provide a Bundle
with the Fragment's arguments in it:
Bundle().apply { putBundle(SOME_SPECIAL_BUNDLE_KEY, arguments) }
By doing so you'll be able to do the following in your ViewModel
:
val fragmentArgs = savedStateHandle[SOME_SPECIAL_BUNDLE_KEY]
val args = YouFragmentArgs.fromBundle(fragmentArgs)
You still need to use a magic key for the Bundle, but it can be a project-wide constant and you only have to pass a single argument. The initialization is also the same for all the ViewModels, so you can easily write helpers to abstract away the process.
si...@gmail.com <si...@gmail.com> #9
If you are using hilt you can wrap the incoming arguments in fragment like so
open class BaseFragment : Fragment() {
override fun setArguments(args: Bundle?) {
if (args != null) {
super.setArguments(Bundle(args).apply {
putBundle(BUNDLE_ARGS, args) // Wrap the arguments as BUNDLE_ARGS
})
} else {
super.setArguments(null)
}
}
}
and then for the view model, you can inherit from something like that
open class ArgsViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
val arguments get() = savedStateHandle.get<Bundle>(BUNDLE_ARGS)
@MainThread
inline fun <reified Args : NavArgs> navArgs() = NavArgsLazy(Args::class) {
arguments ?: throw IllegalStateException("ViewModel $this has null arguments")
}
}
Hope that helps
ju...@gmail.com <ju...@gmail.com> #11
Do you know when this will be released?
Description
androidx.navigation:navigation-*:2.6.0
and aboveThis commit b1ac7d6 introduced a regression in how the title is dynamically rendered, and more precisely how the
Bundle
containing parameters is accessed.Previously, it used the default
Bundle.get(key)
method:But after this commit, it now uses
Bundle.getString(key)
:Unfortunately, this breaks basic usage, with other types than
String
, sinceBundle.getString(key)
will returnnull
.Here is a very simple example:
Previously it would have correctly set the title to
"Album #42"
(for example).But since this update, it will render
"Album #null"
!