Pass data/bundle using navigateUp in Android Navigation Component - android

I found the question but does not have solution in code
I want to have data when backpress/manual back happens. I am using navigateUp() to go back. How can I pass data to previous fragment? navigateUp() does not have any facility to pass data to previous fragment. Even I did not find solution using Safe Args. It's passing data forward. I want to have in backward Frad B -> Frag A.
My code to go back to previous fragment
Navigation.findNavController(view).navigateUp()
My question is, How can i get data in previous fragment. I can navigate to Frag A from Frag B using

According to developer.android.com, you can use common for fragments where you want to share data ViewModel using their activity scope.
Here are steps:
Create view model which will keep the data:
class SharedViewModel : ViewModel() {
val dataToShare = MutableLiveData<String>()
fun updateData(data: String) {
dataToShare.value = data
}
}
Observe data changes in Fragment1:
class Fragment1 : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
viewModel.dataToShare.observe(this, Observer<String> { dataFromFragment2 ->
// do something with data
})
}
}
Update data in Fragment2 and if you're handling navigation properly, now, you should be able to receive data changes on Fragment1:
class Fragment2 : Fragment() {
private lateinit var viewModel: SharedViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)
updateDataButton.setOnClickListener { v ->
viewModel.updateData("New data for fragment1")
}
}
}
I hope answer helps.

You can use NavigationResult library. Basically it's startActivityForResult but for Fragments in Navigation component.

Please use the OFFICIAL androidx's components. setFragmentResultListener() and setFragmentResult() methods:
implementation "androidx.fragment:fragment-ktx:1.3.5"
Cheers ;)

You should use static variables/companion objects, because it is better than shared viewmodel as it is not simple/nice architecture. As it it not straightforward, I think it is the best way.
To navigateUp From FragmentB to FragmentA
FragmentB:
isBackpressed = true
findNavController().navigateUp()
FragmentA:
onViewCreated() {
// todo
if(isBackpressed) {
isBackpressed = false
// do whatever you want
}
}

To pop destinations when navigating from one destination to another, add an app:popUpTo attribute to the associated <action> element.
To navigate from fargment2 to Fragment1 with arguments, specify in the navigation graph the action of the caller fragment and the arguments of the destination fragment :
<fragment
android:id="#+id/fragment2"
android:name="com.example.myapplication.Fragment2"
android:label="fragment_2"
tools:layout="#layout/fragment_2">
<action
android:id="#+id/action_2_to_1"
app:destination="#id/fragment1"
app:popUpTo="#+id/fragment1"/>
</fragment>
<fragment
android:id="#+id/fragment1"
android:name="com.example.myapplication.Fragment1"
android:label="fragment_1"
tools:layout="#layout/fragment_1">
<argument
android:name="someArgument"
app:argType="string"
app:nullable="false"
android:defaultValue="Hello Word"/>
</fragment>
In your Fragment2 class, you call your action and pass your argument:
val action = Fragment2Directions.action2To1("MY_STRING_ARGUMENT")
findNavController().navigate(action)

You can just call
findNavController().navigate(R.id.fragment1, args)
where args is your bundle.
In fragment1, fetch the data from the arguments

Related

Fragment Get Destroyed And Created Again Using Android Jetpack Navigation Controller

I have an activity with three fragments. It will navigate between fragments using navigation controller. But everytime i move to other fragment, the previous fragment destroyed.
When I back (using back key or app bar back button), it will called onCreateView again.
The problem is, I have a method called fetchProducts() that should run once when view created on fragment. Because the fragment alwasy get destroyed, so my fetchProducts always get called again and I dont wanna do that.
Im using viewBinding btw.
Here some of my code:
#AndroidEntryPoint
class HomeMainFragment : Fragment(R.layout.fragment_main_home) {
private var _binding: FragmentMainHomeBinding? = null
private val binding get() = _binding!!
private val viewModel: HomeMainViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentMainHomeBinding.bind(view)
setupRecyclerView()
observe()
goToCreateProductPage()
fetchProducts()
}
//...
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
How to keep the fragment so it will not destroyed? Especially using viewBinding
As this issues:
Support multiple back stacks for Bottom tab navigation
You can use navigation library version 2.4.0-alpha04 and fragment version 1.4.0-alpha04 for back stacks support.
And yes you should consider using ViewModel to get data that you fetch.

How to safeargs in viewModel Kotlin Android?

I have a problem with my MVVM structure. I create apps and pass data between fragments. Now it works fine, but I need to add this logic to my ViewModel.
This is my NotesClickFragment:
#AndroidEntryPoint
class NotesClickFragment : Fragment(R.layout.fragment_click_notes) {
private val args by navArgs<NotesClickFragmentArgs>()
private val viewModel: NotesClickViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = FragmentClickNotesBinding.bind(view)
binding.apply {
textViewTitleClick.setText(args.notesClickArgs.titleNotes)
textViewContentNotesClick.setText(args.notesClickArgs.contentNotes)
textViewHistoryClick.setText(args.notesClickArgs.createdNotesDateFormat)
}
}
}
This is my NotesClickViewModel:
class NotesClickViewModel #ViewModelInject constructor(
private val notesDao: NotesDao
) : ViewModel() {
}
I'm trying to add a private argument val navArgs: NotesClickFragmentArgs and create the other functions to set data from fragments but it doesn't work. What is good practice? Thanks in advance for your tips.
NotesDao has no place in ViewModel and instead should be inside Repository.
For more information on MVVM, please read the following: Guide to app architecture
As far as I know, ViewModel cannot be used here. Navigation should be handled inside Fragment itself.
In order to use SafeArgs you first need to declare parameters inside NavGraph and then you can use Directions to navigate to a fragment where you can pass your necessary data.
For example. Let's say I have list of movies in RecyclerView. When I click on any of the movies I want to see id of the movie I clicked in a new fragment.
Step 1: Declare information in NavGraph
This is how Fragment is declared in my NavGraph. The tag argument means that this Fragment accepts Int(id).
<fragment
android:id="#+id/movieDetailsFragment"
android:name="my.test.MovieDetailsFragment"
android:label="fragment_movie_details"
tools:layout="#layout/fragment_movie_details">
<argument
android:name="id"
app:argType="integer" />
</fragment>
Then, in any other Fragment connected to MovieDetailsFragment I simply use Directions and pass id that I need.
val direction = MoviesPopularFragmentDirections.actionPopularFragmentToMovieDetailsFragment(id = movie.id)
this.findNavController().navigate(direction)
And that should be it.
Again, more information in official documentation here Navigation Safe Args

Use MutableStateFlow as Hot stream, Kotlin Android

I am migrating from LiveData to Flow and faced the following problem:
I have a flow in viewModel
class MyViewModel() : ViewModel() {
val state = MutableStateFlow<Boolean>(false)
}
class FirstFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
lifecycleScope.launchWhenCreated {
viewModel.loginPresenterState.startVerifyFragmentEvent.collectLatest {
Log.d("Nurs", "loginPresenterState $it")
if (it)
findNavController().navigate(R.id.action_FirstFragment_to_SecondFragment)
}
}
}
}
when this flow is triggered , My FirstFragment navigates to another fragment "B".
But when I press back button, the state triggers one more time, and instead of navigating to FirstFragment, I am coming back to "B". I suppose this behavior is because Flow is Cold. How to manage it be called only once?
Probably because the states remain same and when you came back it re-observes state and navigates. Check this article and use the EventWrapper that mentioned in the article. He used livedata but same logic applies for stateflow too. article

Does an instance of a SharedViewmodel never dies?

I have an app that has a main activity and fragments depend on it, so this is normal.
Now, two of my 10 fragments need to communicate, which I use the example given here
https://developer.android.com/topic/libraries/architecture/viewmodel.html#sharing
class SharedViewModel : ViewModel() {
val selected = MutableLiveData<Item>()
fun select(item: Item) {
selected.value = item
}
}
class MasterFragment : Fragment() {
private lateinit var itemSelector: Selector
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
itemSelector.setOnClickListener { item ->
// Update the UI
}
}
}
class DetailFragment : Fragment() {
// Use the 'by activityViewModels()' Kotlin property delegate
// from the fragment-ktx artifact
private val model: SharedViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
model.selected.observe(viewLifecycleOwner, Observer<Item> { item ->
// Update the UI
})
}
}
Now, if MasterFragment and DetailFragment dies (both does a popBackStack()) does that instance of the viewmodel keep active untill I finish the MainActivity containing this Fragments ? Because now I dont need anymore that viewmodel instance, but as per documentation says, this instance will be retained from the Activity that contains these fragments
This is not what I'm looking for to communicate between fragments since now a new instance of that viewmodel will be the same as the past one I have created, I mean, it will reuse the instance that I used with the already poped fragments, in which I will need to extra handling a deletion or reset of all the data inside this viewmodel instead of getting a new fresh viewmodel.
Does it works this way or that instance automatically dies when no fragments depending on it are in the stack anymore ?
Now, if MasterFragment and DetailFragment dies (both does a popBackStack()) does that instance of the viewmodel keep active untill I finish the MainActivity containing this Fragments ?
Correct. While it so happens that only two of your fragments use it, that ViewModel is scoped to the activity.
I mean, it will reuse the instance that I used with the already poped fragments, in which I will need to extra handling a deletion or reset of all the data inside this viewmodel instead of getting a new fresh viewmodel.
Then perhaps you should not be using activityViewModels(). For example, you could isolate these two fragments into a nested navigation graph and set up a viewmodel scoped to that graph.
Does it works this way or that instance automatically dies when no fragments depending on it are in the stack anymore ?
The ViewModel system does not know about what is or is not "depending on it". It is all based on the ViewModelStore and the ViewModelStoreOwner that supplies it. activityViewModels() uses the activity as the ViewModelStoreOwner, so viewmodels in that ViewModelStore are tied to the activity.

How can I pass data to the first Fragment whilst using the Navigation Architecture?

I'm trying to pass a bundle of object instances down from my main activity to the first fragment in a chain of other fragments using the NavHostFragment. I've tried all sorts but the bundle always seems to be null once it reaches the first fragment.
Here's how I'm initiating the NavHostFragment (frameContainer is a Frame Container element in my layout xml)
NavHostFragment navHost = NavHostFragment.create(R.navigation.claim_nav_graph);
getSupportFragmentManager().beginTransaction()
.replace(R.id.frameContainer, navHost)
.setPrimaryNavigationFragment(navHost)
.commit();
The documentation says there are 2 different .create functions, one of them you can pass a second arguments to as a bundle, but Android Studio doesn't allow me to use this version.
Does anyone have any ideas?
Thanks in advance!
It does seem to be a flaw with the NavHostFragment, passing data down to the first fragment does not seem to be possible, as the Bundle you can set as a second argument on the create function is overwritten along the way.
In the end I resolved this by building the bundle in the first fragment of the activity instead. I was able to access the activities intent properties using the below.
// Kotlin
activity.intent?.extras?.getBundle(KEY_BUNDLE_ID)
// Java
getActivity().getIntent().getBundleExtra(KEY_BUNDLE_ID)
This was enough of a workaround for me in this situation, but it would be great if it was possible
If you're using viewModels, you can do this:
your viewmodel:
class NiceViewModel: ViewModel() {
var dataYouNeedToPass = "initialValue"
}
your activity:
class MainActivity : AppCompatActivity() {
val niceViewModel: NiceViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
niceViewModel.dataYouNeedToPass = "data You Need To Pass"
}
}
your fragment:
class YourFragment : Fragment() {
private lateinit var niceViewModel: NiceViewModel
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
niceViewModel = (activity as MainActivity).niceViewModel
niceViewModel.dataYouNeedToPass //do whatever you need to do with this
}
}

Categories

Resources