i am really new to android studio and struggle with a very basic problem.
I have seen a few threads regarding that issue but none really tacles my problem exactly.
Problem:
How am i able to change a fragment in the NavigationDrawer template? The template provides a environment in which the so-called content_main.xml should be changed to another fragment. I do not know how to accomblish that
What I tried: Since I was not able to change the content_main.xml I changed the whole xml, which includes the content_main.xml. It is easier understandble if you just have a short look at my code.
Template app_bar_main.xml that sets up how the fragments are suppose to look and includes content_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
android:id="#+id/content_main_frame"
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
tools:showIn="#layout/app_bar_main"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</FrameLayout>
In the Navigation Drawer main_activity I change my fragment like this:
public boolean onNavigationItemSelected(MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
Fragment newFragment;
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
if (id == ..) {
} else if (id == R.id.nav_emergencies) {
transaction.replace(R.id.content_main_frame, new Drawer_Emergencies());
transaction.addToBackStack(null);
transaction.commit();
}}
As you might see the transaction replaces the whole app_bar_main but i think i should just change the content_main.xml that is included in app_bar_main.xml. How do I accomblish that?
As I implemented the suggestion from Jude I got a updated result. The Fragment is at the right place, but somehow the content_main_frame.xml (hello world) is still shown.
maybe you should try to make a framelayout inside your content_main xml file.
then load the new fragment into the framelayout.this is like creating a frame for the new fragment.
then load it like:
transaction.add(R.id.yourframelayoutid, new Drawer_Emergencies()).commit();
From a Discussion with Mike.M I was able to solve my problem, whereas he provided the way. To make this thread closable I want to post the solution in here, citing Mike.M :
If you want to replace some Views with a FragmentTransaction, those Views should be in their own Fragment, one that you could load at startup, in the Activity's onCreate(). Your subsequent replace() transactions will then work as expected, provided that you're passing the same R.id for all of them; i.e., provided that you're swapping them out of the same ViewGroup. (Btw, please put # in front of my username when you'd like to ping me. When there's more than one other user in comments, we don't get notified unless you address it directly to a user.)
Which yields the following step-by-step solution:
I have to make empty Framelayout, e.g. make my content_main.xml empty.
Make a seperate Fragment for the inital content and load that in content_main.xml at the start..
After opening a new drawer I the fragment container, which is my Framelayout with a new fragment..
You can do something like this:
content_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior"
android:id="#+id/content_frame">
</FrameLayout>
and then
transaction.replace(R.id.content_frame, new Drawer_Emergencies());
Related
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!
Template that provide Android Studio
This is how I try to do. What is the R.id.x?. I tried using layout id in xml ActivityMain but not the fragment is displayed or overlaid with out activity information.
After selecting one of the items, the fragment will be displayed above the activity. The activity has several elements belonging to material design as the FAB or the Toolbar, Could this create problems with what I do?
In activity_main.xml try replacing:
<include
layout="#layout/app_bar_main"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
With this:
<FrameLayout android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And then use R.id.fragment_container as your R.id.x.
This will however replace the view with the FAB and coordinator layout. But you will probably want to add them to the fragments where you want them.
If not you could try replacing the xml found below in app_bar_main.xml, or wrap it in the FrameLayout.
<include layout="#layout/content_main"/>
Update:
Replacing in activity_main.xml will cause some issues as that removes the toolbar.
If you replace the include in app_bar_main.xml your fragments will most likely get overlapped by the toolbar at the top. To prevent this you could try adding app:layout_behavior="#string/appbar_scrolling_view_behavior" in the FrameLayout snippet above.
R.id.x is the id of the View that your fragment going to be replaced. So in your layout R.layout.xyz you have created a layout
<FrameLayout android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
and add/replace the fragment to that id. (R.id.fragment_container) such that your new fragment will be placed in that layout.
transaction.add(R.id.fragment_container, firstFragment)
At the end solved the problem using some of the recommendations made in the answers they provided me.
Change content_main.xml content. Using a FrameLayout with its correspodiente id.
Add fragment on onNavigationItemSelected(MenuItem item)
Result
When I press a menu item ...
...the fragment is displayed
I want to do something like that. An picture is easier than words:
And when the user clicks on a button on the Fragment B, the fragment B changed but not the A.
I've made two different layout (A portrait and a land one). The first one has a layout like
<?xml version="1.0" encoding="utf-8"?>
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.my.app.ContactsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/contacts_fragment" />
I've a button in my fragment layout with a simple activity call:
Intent intent = new Intent(getActivity(), NextActivity.class);
startActivity(intent);
And the land one is like that:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.my.app.ContactsFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/contacts_fragment" />
<fragment
xmlns:android="http://schemas.android.com/apk/res/android"
android:name="com.my.app.HomeFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/fragment_container" />
</LinearLayout>
I change the inner fragment using the following code:
FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
NextFragment nextFrag = new NextFragment();
ft.replace(R.id.fragment_container, nextFrag);
ft.addToBackStack(null);
ft.commit();
This part works well.
I have two questions now:
How to put these two ways to change the content in the main activity? I mean, in the main activity, a fragment should call the second way, but in a normal activity, I need to call the first.
If I click on an item in the fragment A, and then I click on the button in fragment B that changes the fragment to NextFragment. If I click on another item and i do the same. I can go back to the first user. Is there a way to dump the stack when clicking on a new item ?
Thanks for your help.
Ps: I'm using the native fragment lib not the support v4.
I'm struggling to understand the specifics of your 2 questions, as they are vague and do not give enough detail.
However, since you want your Fragments to be changed out at runtime, you should not put <fragment/> in your layout files. Your current architecture makes it impossible to change out the Fragments in your layouts, which is not what you want.
Note: When you add a fragment to an activity layout by defining the fragment in the layout XML file, you cannot remove the fragment at runtime. If you plan to swap your fragments in and out during user interaction, you must add the fragment to the activity when the activity first starts.
You should be using FrameLayout containers for your Fragments in your layout files, and have a single Activity add Fragments to those FrameLayout containers depending on if they are there. This will allow the app to create 1 Fragment in portrait and 2 Fragments in landscape (given you have have a layout for each orientation). This will also allow you to be able to swap out Fragments as your please, and add them to the back stack.
And example of this Google recommended approach can be found here.
You can find a good solution from here.
Good day, trying to clarify this: if i have this layout for a portrait layout
main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<fragment
android:id="#+id/configFragment_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.settingsFragment">
</fragment>
and this for landscape(mainly for tablets), main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<fragment
android:id="#+id/configFragment_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.example.SettingsFragment" ></fragment>
<fragment
android:id="#+id/detailFragment_id"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="com.Example.DetailFragment" >
<!-- Preview: layout=#layout/details -->
</fragment>
</LinearLayout>
now within my SettingFragment, i implement a button to go to the DetailFragment:
DetailFragment fragment = (DetailFragment)getActivity().getSupportFragmentManager().findFragmentById(R.id.detailFragment_id);
FragmentTransaction fragmentTransaction = getActivity().getSupportFragmentManager().beginTransaction();
if (fragment != null && fragment.isInLayout()) {
DetailFragment detailfragment = new WallpaperFragment();
fragmentTransaction.add(R.id.detailFragment_id, detailfragment);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}else{
Intent intent = new Intent(getActivity().getApplicationContext(),DetailActivity.class);
startActivity(intent);
}
Am i now implementing a dynamic fragment or am i still using static fragment?.. or do i only use dynamic fragment when i use a viewgroup like FrameLayout in place of the fragment tag element?
Idea is to have a single pane for phones and multi-pane for tablets.I know its a bad idea to mix static and dynamic fragments, but its kind of confusing. Thank you.
You know, I might be wrong here (so feel free to correct me) but it looks like you're using both static AND dynamic fragments. In your XML layouts you've added the fragments then you are re-instancing them via a fragment transaction in your activity. Alternatively, if you had declared a container (FrameLayout for instance) instead of a fragment in your XML you would have to use a FragmentTransaction to add the fragment at run time.
In your example you are just stepping on the fragment that you all ready have. Basically, the OS sees your fragment in the XML when inflating (I think that's when it calls the fragment code?) and executes the code associated with it via the tag in the fragment. Your app then adds that same fragment over the top of itself. A simple way to show this is to add a LogCat call in your Fragment class's onCreateView method. If you see it more than once, then your stepping on the previous fragment with the same fragment (which I'm 99% sure you are). Hope this helps to answer your question!
Static fragments are defined in the layout and are not generally added or removed at runtime. They are referenced by their id in your code. They are generally put as a child element of a layout like below. Once they are defined here, android will know to make a fragment, that's all you have to do.
<LinearLayout 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"
android:orientation="vertical"
android:weightSum="1">
<fragment
android:id="#+id/fooFragment"
android:name="com.example.myapplication.TestFragment"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
Dynamic fragments are defined in your code and can be manipulated, added, removed, etc during runtime. They look like this:
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(R.id.your_placeholder, new TestFragment());
ft.commit();
I am trying to build an app for 3.0 using fragments. On the left side of the application there is a static fragment, and on the right side there is a dynamic one. Each of my fragments in the dynamic portion have a title. Whenever I go to replace the initial fragment, The title of the first is still shown above the title of the first. Successive replacements replace the lower portion but the initial title is still shown (sorry I cannot post images yet).
Best I can do for image:
Initial Header
New Header
Content(displays fine)
main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal" >
<fragment
android:id="#+id/static_fragment"
android:name="ruleout.android.History_Fragment"
android:layout_width="500dp"
android:layout_height="match_parent" />
<fragment
android:id="#+id/dynamic_fragment"
android:name="ruleout.android.New_Screen_Fragment"
android:layout_width="fill_parent"
android:layout_height="match_parent" />
</LinearLayout>
Partial MainActivity used to change fragments
private void open(Fragment toShow)
{
FragmentTransaction fragmentTransaction = FragMag.beginTransaction();
fragmentTransaction.replace(R.id.dynamic_fragment, toShow);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
Please let me know if you need any additional information and thanks in advance for your help.
If you want to dynamically change fragments, you can't declare them in your layout. The Fragment documentation has an example of doing just what I think you are describing, where a layout has a static fragment declared in it along with a FrameLayout for holding a Fragment that will be dynamically added and replaced.