So I am building a standard Android Application with Kotlin. My app implements a TabLayout with one tab holding a RecyclerView List.
Now, this seems simple and I am not sure why it is not working. When I click on one of the items in my list, the listener is responsive (as I have made a few Log test), but the fragment associated to the item does not inflate!
In the OnCreateView() of my Fragment list where I implement my RecyclerView, I instruct the listener in this way:
//Give action to item click in adapter
adapter.onItemClick = {
//Setup new fragment Item
val newFragment = ItemFragment()
// parentFragmentManager is the same as activity?.supportFragmentManager
parentFragmentManager.beginTransaction()
.replace(R.id.list_fragment_id, newFragment)
.addToBackStack(null) //Makes it possible to comeback to recyclerview
.commit()
}
I am thinking my mistake lies somewhere around the ID of the current container as the application is setup in TabLayout. I have tried to use the ID of my view pager or even the RecyclerView, but nothing seems to be successful.
Any help would be extremely helpful!
Try to below code which has in java convert it to kotlin by your self :)
First Declare var in your adapter
Fragment fragment = null;
FragmentManager fragmentManager;
FragmentTransaction fragmentTransaction;
After Add below code in your onclick method
fragment = new CategoryFragment();
fragmentManager = ((FragmentActivity)getContext()).getSupportFragmentManager();
fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.frameLayoutHome, fragment);
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.commit();
Instead of adapter.onItemClick = ...
You should put click listener on ViewHolder view
Adaper class
override fun onCreateViewHolder(parent:ViewGroup, viewType:Int):MyViewHolder{
{
return MyViewHolder(view, myClickListener)
}
ViewHolder class
class MyViewHolder : ViewHolder(v:View, myClickListener:ClickListener?) {
itemView.onClick = { myClickListener?.clicked() };
}
Finally found an answer by myself :')
It does indeed lie in the container of your view.
.replace(R.id.list_fragment_id, newFragment) does not work because you cannot replace a fragment with a layout, it needs to be done with VIEW
.replace(R.id.viewPager, newFragment) does not work because because viewPager is the view representing all your fragments viewing and not just the one you are looking for
.replace(R.id.recyclerview, newFragment) cannot hold another fragment
THUS I have nested my recyclerview in my XML file inside a RelativeLayout who represents the actual CONTAINER that we wish to replace the fragment with!
It should look something like this:
<RelativeLayout
android:id="#+id/recyclerview_container"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#android:color/white"
tools:listitem="#layout/recyclerview_item"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</RelativeLayout>
If I have made a mistake, please let me know!
Related
In my project I have an activity and multiple fragments.
Currently the fragments are declared in my activity xml
e.g:
<fragment
tools:layout="#layout/fragment_do_you_know"
android:name="myapp.fragments.DoYouKnowFragment"
android:id="#+id/doYouKnowFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"/>
<fragment
tools:layout="#layout/fragment_whats_new"
android:name="myapp.fragments.WhatsNewFragment"
android:id="#+id/whatsNewFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="visible"/>
During activity initialization I get fragment references:
mWhatsNewFragment = (WhatsNewFragment) mFragmentManager.findFragmentById(R.id.whatsNewFragment);
mDoYouKnowFragment = (DoYouKnowFragment) mFragmentManager.findFragmentById(R.id.doYouKnowFragment);
On different actions I show one of the fragment and hide all other in FragmentManager transaction:
protected void showFragment(BaseFragment fragment) {
FragmentTransaction transaction = mFragmentManager.beginTransaction();
transaction.setCustomAnimations(android.R.animator.fade_in, android.R.animator.fade_out);
BaseFragment visibleFragment = null;
for (BaseFragment fr: mAllFragments) {
if (fragment == fr) {
transaction.show(fr);
visibleFragment = fr;
}
else {
transaction.hide(fr);
}
}
transaction.commit();
if (visibleFragment != null) {
visibleFragment.onShow();
}
}
I know that the other approach is to use newInstance() factory method for getting the fragment refereces.
In that case I suppose I have to set the layout parameters (layout_width, layout_height) by code.
But I think this is the right way if I want to pass initialization paramters to fragment.
So I wonder which approach is better.
And also is keeping references to all fragments is Ok or is better creating during transaction?
Not at all. when you are creating newInstance factory method you do so because you want to pass some arguments from activity to fragment. normally you would do it with constructor but thats not an option when working with fragments. so thats only reason to create factory method for fragments other times you would just call default constructor. now in either case that doesnt mean that you will need to write layout paramets in code. there is nice workaround for that. you will create FrameLayout or any ViewGroup and set its layout parameters in xml. now at some point when you will want to add your fragment you can just add your fragment(or replace) in that ViewGroup. code is as simple as anything can get.
supportFragmentManager
.beginTransaction()
.replace(R.id.your_view_group_id, BadAssFragment.newInstance(someCoolData))
.commit()
I found so many similar questions and so many different answers, but none of them helped me. I will try to post a clear question to get a clear answer if it's possible.
So I have an Activity that has a ViewPager with FragmentStatePagerAdapter containing (for now) only one fragment (HomeScreenFragment).
Inside HomeScreenFragment I have a RecyclerView with a list of several kind of different icons which should open a different fragment for each item click.
HomeScreenFragment has a FrameLayout as a root layout with id name "container". This is the method I'm calling to replace HomeScreenFragment with MainTypesFragment in this case:
private void openAllTypesFragment() {
MainTypesFragment fragment = new MainTypesFragment();
FragmentTransaction transaction = getActivity().getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.container, fragment);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
transaction.addToBackStack(HomeScreenFragment.class.getName());
transaction.commit();
eventBus.post(new Event(null, Event.EVENT_FRAGMENT));
}
Since FragmentStatePagerAdapter is initialized in MainActivity, I'm sending an event which will MainActivity catch and call adapter.notifyDataSetChanged();
Doing it this way, nothing happens.. I tried with getChildFragmentManager() instead of getActivity().getSupportFragmentManager() and in that case, previous fragment (HomeScreenFragment) is visible underneath new one (MainTypesFragment)..
Any ideas what am I doing wrong here?
The container you are using is wrong.Maintain one container in the activity xml and use that container to load all the fragments.
transaction.replace(R.id.container, fragment);
The container here is to be the activity container in which viewpager and other views should be present.
You can try my code :
private void addFilmDetailFragment(Movie movie){
android.support.v4.app.FragmentManager fragmentManager = getFragmentManager();
android.support.v4.app.FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FilmDetailFragment fragment = new FilmDetailFragment();
Bundle bundle = new Bundle();
bundle.putParcelable("movie", movie); // Key, value
fragment.setArguments(bundle);
fragmentTransaction.replace(R.id.mainContentStore, fragment);
// fragmentTransaction.replace(R.id.mainContent, fragment, Config.TAG_FILM_DETAIL);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commit();
}
In a fragment of viewpager :
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/mainContentStore"
android:layout_width="match_parent"
android:layout_gravity="center"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerFilm"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
It worked for me, i hope it will help your problem!
You need container inside activity for ViewPager and inside fragments(pages), containers for opening new fragments inside pages.
And if you want to open new fragment you should user something like this
in page fragment
getChildFragmentManager().beginTransaction()
.replace(R.id.page_fragment_container, fragment)
.addToBackStack(name)
.commit()
where R.id.page_fragment_container container inside page fragment.
Good morning. I'm developing an app in which I have a main layout that extends from activity and inside this one I have one fragment of one data type, in my case FragmentCover (it's a class).
During my app, I push a button and I want to change this fragment layout for another layout that extends from fragment but of different type, called SongList.
My problem is that I have defined this fragment for the class of Cover and when I change I don't have any problem, but when I want to get the views and set to one variable of my class, the funciont songList = (ListView) getView().findViewById(R.id.songList); it's null and it gives me error.
I put it here what I do.
layout
<fragment android:name="es.xxx.ui.FragmentCover"
android:id="#+id/pruebaa"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_above="#id/songBar"
android:layout_below="#id/header"/>
mainclass change
public void onClick(View v) {
if(isCoverFragment){
FragmentSongList fragmentSongList = new FragmentSongList();
transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.pruebaa, fragmentSongList);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.addToBackStack("LIST");
transaction.commit();
isCoverFragment=false;
}
else{
transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.pruebaa, fragmentCover);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.addToBackStack("COVER");
transaction.commit();
getSongCover();
isCoverFragment=true;
}
The problem is not the change, is when I try to make the findViewById, it's probabbly because it doesn't load the view associated with FragmentSongList.
You need to use a FrameLayout inside your layout file, instead of defining the Fragment in .xml
Into that FrameLayout, you can inflate any Fragment you want.
Just inflate the First Fragment you want to be displayed directly in your onCreate(...) method.
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
This is how to inflate the Fragments programatically into the FrameLayout.
FragmentSongList fragmentSongList = new FragmentSongList();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
transaction.replace(R.id.content_frame, fragmentSongList);
transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
transaction.addToBackStack("LIST");
transaction.commit();
You can leave the code inside your onClick() method just the way it is, just change the id to "content_frame". Furthermore, as mentioned above you will have to inflate the first Fragment that should be displayed inside your onCreate(...) method.
I have a tab + ViewPager layout and in one of these tabs I have a list view. When I replace that list fragment upon the onclick I can still see the old fragment under the new fragment. See:
Code:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction transaction = fragmentManager.beginTransaction();
HallsInStateFragment hallsForState = new HallsInStateFragment();
transaction.replace(R.id.container, hallsForState);
transaction.addToBackStack(null);
transaction.commit();
where the R.id.container is the FrameLayout in the view.
when need to remove all views from the parent view you need to call removeAllViews() at container in your onCreateView() method of your fragment.
Here is the code:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
container.removeAllViews(); // Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_example, container, false);
}
Instead of R.id.container put id of fragment like this: ((ViewGroup)getView().getParent()).getId(). Actually it is not replacing the fragment but the previous layout i.e FrameLayout. It works for me and i hope it will work in your case also.
Add this to both fragment parent layout
android:background="#android:color/white".
The one which you are replacing and one which you will replace.
The fragment's UI is a part of the activity view hierarchy. So if you created your views in onCreateView() method then you inflate your layout using the ViewGroup container. This container keeps references to your fragment views. Try to override onDestroyView() method of your fragment and remove all the views from the parent:
#Override
public void onDestroyView() {
//mContainer.removeAllViews();
ViewGroup mContainer = (ViewGroup) getActivity().findViewById(R.id.container);
mContainer.removeAllViews();
super.onDestroyView();
}
Your fragments should be loaded in FrameLayout like this
<FrameLayout
android:id="#+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?android:windowBackground" />
And your fragments should be added/loaded in this frameLayout by this function
private fun switchFragment(
fragment: Fragment,
addToBackstack: Boolean
) {
//check new fragment is alredy loaded currently, then return
val myFragment =
supportFragmentManager.fragments.lastOrNull()//return current visible fragment or null
if (myFragment != null && fragment::class == myFragment::class) {
return
}
val fragmentManager = supportFragmentManager
val transaction = fragmentManager.beginTransaction()
//transaction.add(R.id.frameLayout, fragment, fragment.javaClass.name)
transaction.replace(
R.id.frameLayout,
fragment,
fragment.javaClass.name
)//using replace will make sure that the previous fragment won't be visible from new fragment
if (addToBackstack) {
transaction.addToBackStack(fragment.javaClass.name)
}
transaction.commit()
}
So initially, your first fragment should be loaded like this,
switchFragment(HomeFragment(), false)
and then other fragments when you select from bottom navigation view or navigation drawer, call this function like this
switchFragment(MyProfileFragment(), true)
In My Case , This was happening because I was using the static fragment as
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<fragment
android:name="com.example.android.FooFragment"
android:id="#+id/fooFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
where foofragment is the initial fragment , Then Trying to replace the fragment using with other fragment , so that both fragment overlaps.
Instead , Problem is solved , when I used dynamic Linkage , in place of fragment in the xml , we need to use framelayout as
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<FrameLayout
android:id="#+id/your_placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent">
</FrameLayout>
</LinearLayout>
Then dynamically adding the fragment-1 and replacing with the fragment-2 , is working fine for me.
I did not find answer earlier, hence posting this solution.
In the root view add clickable = true and add a background color(both in the xml) of the fragment replacing the current fragment .
Its just a fix for workaround
Best code for it. clearly and not used another ram of device.
on you Activity onCreate or before adding fragments add this code:
for (Fragment fragment : getSupportFragmentManager().getFragments()) {
if (fragment instanceof NavigationDrawerFragment) {
continue;
}
else if (fragment != null) {
getSupportFragmentManager().beginTransaction().remove(fragment).commit();
}
}
Happy coding :)
My requirement is quite simple: I have a button that should replace a FragmentA by FragmentB.
This sounds easy and nearly work, the big problem is that the old fragment is not removed and the new placed on the front of the old one and they are "living" together in my layout.
The Code:
FragmentManager fragMgr = a.getSupportFragmentManager();
Fragment currentFragment = (Fragment) fragMgr.findFragmentById(R.id.fragmentitself);
if(currentFragment!=null){
FragmentTransaction fragTrans = fragMgr.beginTransaction();
fragTrans.remove(currentFragment);
FragmentB newFragment = new FragmentB();
fragTrans.replace(R.id.fragmentcontainer, newFragment);
// I have also tried with R.id.fragmentitself
fragTrans.addToBackStack(null);
fragTrans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragTrans.commit();
}
The Layout:
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:id="#+id/fragmentcontainer">
<fragment
android:id="#+id/fragmentitself"
android:name="com.WazaBe.MyApp.FragmentA"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
Solution
First, you have to remove your fragment from XML and just keep empty container there:
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:id="#+id/fragmentcontainer" />
Then you need to add your com.WazaBe.MyApp.FragmentA fragment from code, i.e. in onCreate() of your parent Activity.
Explanation
It is because your transactions manipulate content of ViewGroup such as said FrameLayouts. The catch is, that you can only manipulate elements you added from code as whatever is inflated from XML layout is considered "read-only". So when you put your Fragment directly into your XML layout, then it becomes permanent part of the view hierarchy and because it is permanent and the whole hierarchy is "read-only" it cannot be removed from code.
Once you get your layout fixed and Fragment extracted, the remove() call is no longer needed - it will suffice to just do replace().
If you want to place a Fragment in a View Container(Such as a Framelayout),you must make sure that your container is empty(only this you can put a fragment into it).you cannot replace a fragment writed in XML file,you should add A into the container by JAVA code ,and when you don't neet id ,you can replace it by B;
at first ,your container is empyt:
<FrameLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2"
android:id="#+id/fragmentcontainer">
</FrameLayout>
OK,you put FragmentA into it:
FragmentTransaction fragTrans = fragMgr.beginTransaction();
fragTrans.remove(currentFragment);
FragmentA fragA= new FragmentA();
fragTrans.add(R.id.fragmentcontainer, fragA).commit();
NOW,if you want to replace:
FragmentTransaction fragTrans = fragMgr.beginTransaction();
FragmentB newFragment = new FragmentB();
fragTrans.replace(R.id.fragmentcontainer, newFragment);
// I have also tried with R.id.fragmentitself
fragTrans.addToBackStack(null);
fragTrans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
fragTrans.commit();
if we use Fragment container on activity, it require select one default FragmentABC, the default FragmentABC view cannot removed even we use the ft.remove.
Use FrameLayout container instead of Fragment container if you want a empty view of fragment on activity startup.
Same Answer In Kotlin
private fun showDetailView(position: Int) {
val fragTrans: FragmentTransaction = supportFragmentManager.beginTransaction()
val fragment: Fragment
when (position) {
0 -> fragment = FragmentA()
1 -> fragment = FragmentB()
else ->
fragment = FragmentC()
}
fragTrans.replace(
R.id.itemDetailContainer,
fragment).commit()
fragTrans.addToBackStack(null);
fragTrans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
}
}