Status Update
Comments
da...@sucharda.cz <da...@sucharda.cz> #2
jb...@google.com <jb...@google.com> #3
da...@sucharda.cz <da...@sucharda.cz> #4
da...@sucharda.cz <da...@sucharda.cz> #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")
}
jb...@google.com <jb...@google.com>
cl...@google.com <cl...@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.
ap...@google.com <ap...@google.com> #7
Could you provide an example of how you achieve that?
pr...@google.com <pr...@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.
Description
Component used: Navigation
Version used: 2.5.1 and 2.5.2
Last known working version: 2.3.5
Devices/Android versions reproduced on: Huawei P20 Light / AN 9 (EMUI 9.1.0)
So our navigation is kinda complex so I will try to simplify the configuration. If there is a nested graph with arguments which cannot be null (and no default value is supplied) and
popUpTo
is used to remove nested graph during destination (nested) navigation it tries to recreate nested graph as a parent for the destination being navigated to.So now if the navigation is triggered like this:
It will pop fragments from back stack, but it will not change parent for
ChildFragment2
which will be set tochildNavGraph
and since it is not in the back stack anymore it will try to recreate it with default values. ButargumentLong
is not nullable and has no default value so it will crash:Note: Interesting thing is that it does not crash with non-nullable String argument for some reason (ah I see, it is because String NavType is nullable by default:
StringType: NavType<String?>
vsLongType: NavType<Long>
).Possible fixes for us:
popUpTo
to pop parent (navGraph) of a Fragment (node) from XML but usepopBackStack
from code instead.argumentLong
.Basically I am not sure if this an intended behavior or a bug. Gonna mark it as a bug because it worked in 2.3.5. I understand that Destination should have a parent but what is the reason to create a parent that does not exist and will never be displayed in this case, since it was already poped and thus it should not be displayed to the user.
At the moment two solutions come to mind:
LongType
and other types nullable same asStringType
. Again not sure if this can be done and it probably is not a proper solution (change).There might of course be other solutions that I cannot see.
Thank you for your time and help.