I have a BottomSheetDialogFragment that contains a NavHostFragment like so:
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/add_feeling_nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="0dp"
app:defaultNavHost="true"
app:navGraph="#navigation/add_feeling_graph" />
</LinearLayout>
</layout>
When I try to get the NavController in it's class using either of the following:
activity?.findNavController(R.id.add_feeling_nav_host_fragment)
view.findNavController()
The first option crashes because it cannot find the ID and the second one does finds a parent Nav Controller and uses that.
I have an identical setup for another Fragment however it's not a dialog and it works perfectly. Any ideas to what the issue could be? Thanks
activity?.findNavController(R.id.add_feeling_nav_host_fragment) won't do the job - it will work only if the dialog fragment is active(showing) and it is a part of activity`s UI container - DialogFragment works in other instance of window that is not within the same hierarchy with the root nav graph.
Appropriate way to do it - with NavHostFragment.findNavController(fragment) or ktx extension fragment.findNavController() where fragment is your dialog fragment. The dialog fragment should be showing with its view inflated. The best way to do it - within the dialog.
Navigation graph adjustments may also be required - I cannot tell exactly since there is no code.
Hope it helps.
Well, first of all you should post more code for us to see the whole picture. One small detail is that you dont need neither <layout> and <LinearLayout> in navigation xml. Fragment is just ok. I will suggest you to look at this repository, there is exactly solution you need.
I Hope it helps.
Related
Main issue: a fragmentcontainerview works without me wanting it to.
Explanation: I have a fragmentcontainer view in an activity.It is a DialogFragment navigation graph, I want it to only shown when I prompt it but it gets shown when activity is started.
Code in activity xml:
<androidx.fragment.app.FragmentContainerView
android:id="#+id/dialogNavigation"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:defaultNavHost="false"
app:navGraph="#navigation/nav_dialog"
tools:layout="#layout/tutorial_dialog_start" />
Setting its visibility gone doesnt help and I couldnt find any other solution
Edit:
Solution 1:
If there's 2 navgraphs: 1 for fragments, 1 for dialogs. Merge them and use actions to navigate to dialogs from specific fragment.
Solution 2:
Create an empty dialog and have it as the first dialog showed in FragmentContainerView, when you want to show the actual dialogs navigate from the empty dialog to the actual dialog
When using androidx.fragment.app.FragmentContainerView as a navHost instead of a regular fragment app is not able to navigate to a destination after orientation change.
I get a following error:
java.lang.IllegalStateException: no current navigation node
Is there a gotcha that I should know about to use it properly or is my way of using nav components is incorrect?
Simple activity xml with a view:
...
<androidx.fragment.app.FragmentContainerView
android:id="#+id/nav_host_fragment"
android:name="androidx.navigation.fragment.NavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
app:navGraph="#navigation/nav_simple" />
...
Navigation code:
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/nav_legislator.xml"
app:startDestination="#id/initialFragment">
<fragment
android:id="#+id/initialFragment"
android:name="com.example.fragmenttag.InitialFragment"
android:label="Initial Fragment"
tools:layout="#layout/initial_fragment">
<action
android:id="#+id/action_initialFragment_to_destinationFragment"
app:destination="#id/destinationFragment" />
</fragment>
<fragment
android:id="#+id/destinationFragment"
android:name="com.example.fragmenttag.DestinationFragment"
android:label="Destination Fragment"
tools:layout="#layout/destination_fragment" />
</navigation>
Here is a github repo where you can easily reproduce a bug: https://github.com/dmytroKarataiev/navHostBug
The no current navigation node error occurs when there's no graph set and you attempt to call navigate(). If it only occurs when you're using FragmentContainerView and after a configuration change, then this would be related to this bug, which is fixed and scheduled for release with Navigation 2.2.0-rc03.
To work around this issue, you can either switch back to <fragment> or remove app:navGraph="#navigation/nav_simple" and instead call navController.setGraph(R.navigation.nav_simple).
I do not see any explanation why replace Fragment view with FragmentContainerView. This is why I added this answer (source below).
One of the common patterns to host fragments in an activity is to use a FrameLayout. The Android community have been doing this for years but this is going to change now. The androidx team introduced this new view called FragmentContainerView to do this for you.
All you have to do is replace your FrameLayout with FragmentContainerView and everything should work out of the box, you won’t need to change anything on the way you handle fragment transactions.
The benefits gained here is the improved handling of what’s called the z-ordering for fragments. Here’s the example they used, but basically this means , for example, that the exit and enter transitions between two fragments will not overlap each other. Instead this FragmentContainerView is going to start the exit animation first then the enter animation.
If you are asking can this replace <fragment> tag? then the answer is yes. All you have to do is to add android:name="your_class_name" when defining FragmentContainerView and it will use a FragmentTransaction under the hood to build and show your fragment. This mean you can use fragment transactions later on to replace it.
Source
Android Docs
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.frag_splash, container, false)
}
Maybe you forgot the 3rd "false" parameter above. Only this three parameters onCreateView fun can be accept.
android developer website
pic from : https://developer.android.com/reference/androidx/fragment/app/FragmentContainerView
// drawer layout that I pass is the id of the layout in the activity from which I move to fragment//
SacnFragment sacnFragment = new SacnFragment();
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.drawer_layout, sacnFragment);
transaction.addToBackStack(null);
transaction.commit();
I'm having a problem when dealing with multiple NavHosts. This issue is very similar to the one asked here. I think the solution for this question would help me as well, but it's a post from 2017 and it still has no answer. Android Developers Documentation doesn't help and searching through the web shows absolutely nothing that could possibly help.
So basically I have one Activity and two Fragments. Let's call them
FruitsActivity, FruitListFragment, FruitDetailFragment, where FruitsActivity has no relevant code and its xml layout is composed by a <fragment> tag, serving as NavHost, like that:
<fragment
android:id="#+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/fruits_nav_graph"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
The FruitListFragment is the startDestination of my NavGraph, it handles a list of fruits that will come from the server.
The FruitDetailFragment shows details about the Fruit selected in the list displayed by FruitListFragment.
So far we have Activity -> ListFragment -> DetailFragment.
Now I need to add one more Fragment, called GalleryFragment. It's a simple fragment that displays many pictures of the Fruit selected and it's called by FruitDetailFragment when clicking a button.
The problem here is: In Portrait mode I simply use findNavController().navigate(...) and I navigate through the Fragments like I want. but when I'm using a Tablet in Landscape mode, I'm using that Master Detail Flow to display List and Details on the same screen. There is an example of how it works here, and I want the GalleryFragment to replace the FruitDetailFragment, sharing the screen with the list of fruits, but so far I could only manage to make it replace the "main navigation" flow, occupying the entire screen and sending the FruitListFragment to the Back Stack.
I already tried to play around with findNavController() method, but no matter from where I call it I can only get the same NavController all the time, and it always navigates in the same linear way.
I tried to implement my own NavHost, but I get and error "class file for androidx.navigation.NavHost not found".
This is the xml of my FruitsActivity:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/listing_nav_graph"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</layout>
This is the xml of the FruitListActivity in Landscape mode (in portrait mode there is just the RecyclerView):
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvFruits"
android:layout_weight="3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"/>
<FrameLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/sideFragmentContainer"
android:name="fruits.example.FruitDetailFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</LinearLayout>
</RelativeLayout>
</layout>
And now I want to call the GalleryFragment and make it replace just the <fragment> of id 'sideFragmentContainer' instead of the whole screen (instead of replacing the <fragment> of id fragmentContainer in the Activity's xml).
I didn't find any explanations of how to handle multiple NavHosts or <fragment> inside <fragment>.
So based on that, is it possible to use Navigation Architecture and display a Fragment inside another Fragment? Do I need multiple NavHosts for that, or is there another way?
As suggested by jsmyth886, this blog post pointed me to the right direction.
The trick was to findFragmentById() to get the NavHost directly from the fragment container (in this case, the one sharing the screen with the rest of the Master Detail screen). This allowed me to access the correct NavController and navigate as expected.
It's important to create a second NavGraph too.
So a quick step-by-step:
Create the main NavGraph to make all the usual navigation (how it would work without the Master Detail Flow);
Create a secondary NavGraph containing only the possible Destinations that the Master Detail fragment will access. No Actions connecting then, just the Destinations.
In the main <fragment> container, set the attributes like that:
<fragment
android:id="#+id/fragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/main_nav_graph"
app:defaultNavHost="true"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
The app:defaultNavHost="true" is important. Then the Master Detail layout will look like that:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvFruits"
android:layout_weight="3"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp"/>
<FrameLayout
android:layout_weight="1"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/sideFragmentContainer"
android:name="androidx.navigation.fragment.NavHostFragment"
app:navGraph="#navigation/secondary_nav_graph"
app:defaultNavHost="false"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout>
</LinearLayout>
</RelativeLayout>
</layout>
Again, the attribute app:defaultNavGraph is important, set it to false here.
In the code part, you should have a boolean flag to verify if your app is running on a Tablet or not (the link provided in the beginning of the answer explains how to do it). In my case, I have it as a MutableLiveData inside my ViewModel, like that I can observe it and change layouts accordingly.
If is not tablet (i.e. follows the normal navigation flow), simply call findNavController().navigate(R.id.your_action_id_from_detail_to_some_other_fragment). The main navigation will happen using the main NavController;
If is tablet using the Master Detail Flow, you must find the correct NavHost and NavController by finding the <fragment> that contains it, like that:
val navHostFragment = childFragmentManager.findFragmentById(R.id. sideFragmentContainer) as NavHostFragment
And finally you can navigate to the Fragment that you want to appear dividing the screen with the rest of the Master Detail screen by calling navHostFragment.navController.navigate(R.id.id_of_the_destination). Notice that here we don't call Actions, we call the Destination directly.
That's it, simpler than what I thought. Thank you Lara Martín for the blog post and jsmyth886 for pointing me to the right direction!
I searched a solution everywhere but I didnt find anything, so I need one more time your help.
I have a Navigation drawer who works perfectly
Navigation drawer
I have also a ViewPager, that I have take on the Android developer's website. It works fine also.
Now I would like to put the PageViewer in my "jour" tab for exemple. But the PageViewer is not a "Fragment" but a "ActivityFragment" so I cannot. The help that I can find on the other threads does'nt match to my problem
How Can I do this ?
Thanks a lot for reading me.
Annex:
Tutorial Android : https://developer.android.com/training/animation/screen-slide.html
I dont know if i should create a new thread or not. I have put the code in the main activity but the fragment cover all the activity. screen of the fragment
It is strange because the other fragments work.
main Activity
TestSlideFragment fragment = new TestSlideFragment();
fragmentTransaction.replace(R.id.RelativeLayout_for_Fragment, fragment);
fragmentTransaction.commit();
layout of the fragment
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="oytoch.iut_info.test.TestSlideFragment">
<!-- TODO: Update blank fragment layout -->
<android.support.v4.view.ViewPager
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/page"
android:layout_width="match_parent"
android:layout_height="match_parent" />
Open your Jour's xml and add the ViewPager Tag.
Then open Jour's java and bind an object of ViewPager with the xml. Add the adapter to the viewpager object and you are done.
Update - Thanks to #Martijn00 and #Plac3Hold3r I have managed to update my app to use the MvxCachingFragmentCompatActivity but it is just not working correctly. I am finding that if I go back sometimes the the ViewModel will be available, but the commands in the view model will be null.
Also if I go back and then forward, some of the buttons don't respond. I assume this is the same issue.
What I really need to know is what the additional functionality the caching activity gives me, and how to use it properly.
Original question follows...
I am hitting a problem where the view model for a fragment is null. I suspect my activity is being cleared up when I open a camera activity. For all my application am using a single activity and all my views are fragments.
When the camera activity is complete, the activity is reconstructed, but one of the fragments view model is null. I am currently using an AppCompatActivity for my single activity, but through my research I should probably be using a MvxCachingFragmentActivity. The problem is I have no idea how I am supposed to use it. I cannot find a clear explanation anywhere.
Has anyone got a working example of how to use the MvxCachingFragmentActivity or the MvxCachingFragmentCompatActivity.
I can't find anywhere where it tells me how I should use it.
I found this other link example but I think it is out of date and the other link given in this example is a 404.
If anyone knows of a simple sample and whether this will work with a single activity please let me know.
Thanks
Setup
MainActivity
Create an activity that inherits from MvxCachingFragmentCompatActivity or MvxCachingFragmentActivity.
[Activity]
public class MainActivity : BaseFragmentActivity<MainContainerViewModel>
{
}
MainContainerViewModel
Create a Viewmodel to associate to the Activity. You will never navigate directly using this Viewmodel. Instead you will navigate to this Viewmodel via the fragments that specifies MainContainerViewModel as their parent's Viewmodel.
public class MainContainerViewModel : MvxViewModel
{
}
XML layout example
Add a layout to your MainActivity. Make sure to include a FrameLayout that has an id. In this case content_frame. This id is important as this is how you will identify the frame where to place your fragment. You specify multiple FrameLayout's if you want more that one fragment for the same view.
<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.AppBarLayout
android:id="#+id/appbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar">
<android.support.v7.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary">
<TextView
android:id="#+id/textview_toolbar_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#android:color/white"
android:layout_gravity="left"
style="#style/TextAppearance.AppCompat.Widget.ActionBar.Title" />
</android.support.v7.widget.Toolbar>
</android.support.design.widget.AppBarLayout>
<FrameLayout
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</android.support.design.widget.CoordinatorLayout>
HomeFragment
In terms of fragments you need to include the MvxFragment attribute, which needs the type of Viewmodel associate to the Activity you want to place your fragment in. Additionally, you need to specify the id for the FrameLayout that is found on the activities layout, where you want to place the fragment.
[MvxFragment(typeof(MainContainerViewModel), Resource.Id.content_frame)]
[Register(nameof(HomeFragment))]
public class HomeFragment : BaseFragment<HomeViewModel>
{
}
Usage
When navigating your can now use ShowViewModel<HomeViewModel>() which will navigate to the home fragment. But, importantly it will first start up the required Activity MainActivity before doing the fragment navigation. This allows for better shared navigation with other platforms which do not require these container Viewmodels, i.e. they get handled automatically via convention.
Notes
You can specify multiple MvxFragment attributes. Is is usefully if you want the same fragment shared under multiple Activities. The Top MvxFragment attribute will be used as the default. If you are currently in the context of any of the other matching MvxFragment attributes then navigation will take place under that activity.
If your Setup.cs is not inheriting from MvxAppCompatSetup or you are using a custom presenter, you need to make sure that you also registering your presenter against IMvxAndroidViewPresenter. This is important as MvxCachingFragmentCompatActivity or MvxCachingFragmentActivity resolve IMvxAndroidViewPresenter in order to navigate to the required fragment.
protected override IMvxAndroidViewPresenter CreateViewPresenter()
{
var mvxFragmentsPresenter = new MvxFragmentsPresenter(AndroidViewAssemblies);
Mvx.RegisterSingleton<IMvxAndroidViewPresenter>(mvxFragmentsPresenter);
return mvxFragmentsPresenter;
}
You can also check out the Sample Repo for an example of this in use.