I am trying to add a fragment, then find a view inside said fragment, and add a view into it. However I keep getting a NullPointerException on this statement
FrameLayout container2 = (FrameLayout) fragment.getActivity().findViewById(R.id.content_frame);
Here is my code. Can someone tell me how to fix this please? thanks
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
Fragment fragment = new FragmentNavigationDrawer();
ViewGroup decor = (ViewGroup) getActivity().getWindow().getDecorView();
View child = decor.getChildAt(0);
decor.removeView(child);
fragmentTransaction.add(decor.getId(), fragment);
fragmentTransaction.commit();
FrameLayout container2 = (FrameLayout) fragment.getActivity().findViewById(R.id.content_frame);
container2.addView(child);
Just use a getter. Set a tag on your fragment so you can access it later, then either call getView() on your fragment to return its root view, or use a getter to access a specific View:
public class MainActivity extends AppCompatActivity {
//In onCreate
if (getFragmentManager().findFragmentByTag(FragmentNavigationDrawer.TAG) == null) {
getFragmentManager()
.beginTransaction()
.add(android.R.id.content, new FragmentNavigationDrawer(), FragmentNavigationDrawer.TAG)
.commit();
}
//Later, when you want to add said View:
FragmentNavigationDrawer frag =
(FragmentNavigationDrawer) getFragmentManager().findFragmentByTag(FragmentNavigationDrawer.TAG)
//Return the root view:
View fragRootView = frag.getView();
//Return a specific view:
frag.getUpdatableViewGroup().addView(newViewToAdd):
}
For your Fragment:
public class FragmentNavigationDrawer extends Fragment {
public static final String TAG = FragmentNavigationDrawer.class.getSimpleName();
FrameLayout updatableViewGroup;
//Can do this inside onCreateView() whilst inflating your Fragment's Views
//That's up to you.
#Override
public void onViewCreated (View view, Bundle savedInstanceState) {
updateableViewGroup = view.findViewById(R.id.updateable_view_group);
}
public FrameLayout getUpdatableViewGroup() {
return updateableViewGroup;
}
Be conscious of the Activity and Fragment life cycles however, and be careful not to attempt to access the Fragment's Views until they have finished inflating - onStart() of your Activity and later should be ok.
Please see the javadoc for FragmentTransaction.commit(). It says it will schedule a change to the fragment back stack. It doesn't happen immediately. It looks like you're expecting the fragment and its views to be instantly available.
Also, I'm really confused why you're reaching in a decor view to make changes. Usually you call out a view by id in the host activity's layout and make changes inside it.
Related
Is that possible I can add a fragment view on the activity view without specifying "fragment" view component in activity's layout xml file? Which function should I look for?
Well, the UI of the fragment has to go somewhere. If you want the entire "content view" to be the fragment, add the fragment to android.R.id.content:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getSupportFragmentManager().findFragmentById(android.R.id.content)==null) {
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, new ToDoRosterListFragment())
.commit();
}
}
Otherwise, somewhere in the activity's view hierarchy, you need a container (usually a FrameLayout) in which to place the fragment's UI. Typically, we do that by putting the container in the layout resource.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager().beginTransaction()
.add(android.R.id.content, MyFragment.newInstance())
.commit();
//Some of your code here
}
android.R.id.content is the container of entire app screen.
It can be used with Fragment:
The android.R.id.content ID value indicates the ViewGroup of the entire content area of an Activity.
The code above will insert the View created by Fragment into the ViewGroup identified by android.R.id.content.
Simply, while creating a fragment we have to replace or add fragment's view with a view present in our application. To replace or add a fragment's, we normally add a Framelayout or any other layout view(as a fragmentview container) in activity or in a fragment.
Now, If you want to replace or add a fragment's view without adding a extra view container in your activity. You can simply do it by accessing the view's provided by AppCompatActivity or Activity.
Now, you can create a fragment, without adding a view container in your activity you can create as,
YourFragment fragment = new YourFragment();
transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(android.R.id.content, fragment); //here, android.R.id.content is a view on which your fragment's view is replaced
transaction.commit();
You need to have a layout in your activity to contain the fragment (preferably a FrameLayout).
<FrameLayout 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/container"
android:name="com.gdevelopers.movies.movies.FragmentMoreMovies"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior" />
Then in activity put this code.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, new YourFragment())
.commit();
}
}
If you don't want to go with #CommonsWare answer, then you'll have to provide a container by code. then function you need is....
setContentView(View)
Yep. If you check the activity class code, you'll see that setContentView can be called with an INT (layouts are identified with ints), or with Views. Therefore, you can create a viewgroup instance on the fly, keep a reference to it (you would need to do the same with an XML generated view), and add your fragments there. This is possible because XML files are just arguments which the view factory class, Inflater, uses to find which view subclasses has to instantiate, using a set of parameters provided in the XML. And obviously, you can do that by hand. Just pick whatever layout class you want to use, for example, FrameLayout, and:
public class Activity extends AppCompatActivity{
private FrameLayout root;
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
root = new FrameLayout(this);
root.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
setContentView(root);
//go on
}
}
use android container instead of custom container like
fragmentTransaction.replace(android.R.id.content,yourFragment);
1.use getWindow().getDecorView() to get a DecorView(FramLayout)
2.add a container view to DecorView
3.add Fragment to the container view
LinearLayout llRoot = findViewById(R.id.parent);
FragmentManager fragMan = getSupportFragmentManager();
FragmentTransaction fragTransaction = fragMan.beginTransaction();
YourFragment yourFragment = (YourFragment)fragMan.findFragmentByTag("yourFragment");
if (yourFragment == null) {
yourFragment = new YourFragment();
}
fragTransaction.replace(llRoot.getId(), yourFragment, "yourFragment");
fragTransaction.commit();
llRoot is a LinearLayout which contains different view object of your activity
If you don't want to allocate a specific place in the view to the fragment container you can always use the RelativeLayour. I guess without a container we cant place a fragment in a view.
If you have any View or say root view from the activity window, get its Id using view.getId() (relevant to android:id) and pass that id to the fragmentTransaction.add() as the container id. Here's just a sample:
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
View view = new View(this);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.add(view.getId(), new Fragment());
}
In my project, I want to set visibility of fragments buttons from MainActivity. But the problem is, it gives NullPointerException(). I also maked listBtn & gridBtn as static. I used below code :
FirstFragment fragment = (FirstFragment)getSupportFragmentManager().findFragmentById(R.id. <frameLayout Id>);
main_page_fragment.listBtn.setVisibility(View.GONE);
main_page_fragment.gridBtn.setVisibility(View.GONE);
You cannot access to your fragment view from Activity class because activity uses its own view (ex: R.layout.activity_main). Rather you can set visibility in your corresponding fragment class which will do the same job.
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.details, container, false);
Button listBtn = (Button)view.findviewById(R.id.listBrn);
Button gridBtn = (Button)view.findviewById(R.id.gridBrn);
listBtn.setVisibility(View.GONE);
gridBtn.setVisibility(View.GONE);
return view;
}
Fragment onCreateView callback is called after onCreate method of activity, so i think you have tried to get access from it. That views will be accessible only after onResumeFragments callback is called, you should perform your actions with fragments there.
Another tip is that you strongly should not call views of fragments directly like you did or via static reference to views that's the worst. You should avoid such dependencies on fragments inner implementation. Instead of it, better is create some method like setInitialState (the name depends on your business logic) and just call it from activity.
So result code:
In activity:
private FirstFragment fragment;
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//init fragment here
}
#Override
protected void onResumeFragments() {
super.onResumeFragments();
fragment.setInitialState();
}
In fragment:
//this will be called on fragment #onResume step, so views will be ready here.
public void setInitialState() {
listBtn.setVisibility(View.GONE);
gridBtn.setVisibility(View.GONE);
}
If you add your fragments dynamically from MainActivity like so:
YourFragment fragment = new YourFragment();
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.fragmentContainer, fragment, YOUR_TAG)
.commit();
Then you can define method in your fragment like so:
public void hideButtons()
{
yourBtn.setVisibility(View.GONE);
}
And call it from activity:
fragment.hideButtons();
I struggle with this for several hours and I found a much simpler solution.
Inside the fragment, simply make a public function (outside the on create view method) with the behavior that you want.
fun hideElement() {
binding.button.visibility = View.GONE
}
And then in main activity access to the fragment and call the function.
binding.bottomNavigation.setOnNavigationItemSelectedListener {
when (it.itemId){
R.id.someFragment -> someFragment.hideElement()
}
}
I'm using a widget called SwipeRefreshLayout, to refresh my fragment when someone pushes the view.
To recreate the activity I have to use:
SwipeRefreshLayout mSwipeRefreshLayout;
public static LobbyFragment newInstance() {
return new LobbyFragment();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_lobby, container, false);
receiver = new MySQLReceiver();
rlLoading = (RelativeLayout) view.findViewById(R.id.rlLoading);
gvLobby = (GridView) view.findViewById(R.id.gvLobby);
updateList();
mSwipeRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.mSwipeRefreshLayout);
mSwipeRefreshLayout.setColorSchemeResources(R.color.pDarkGreen, R.color.pDarskSlowGreen, R.color.pLightGreen, R.color.pFullLightGreen);
mSwipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
#Override
public void onRefresh() {
getActivity().recreate();
}
});
return view;
}
But I don't want to recreate the full activity that contains the view pager, I would like to recreate the fragment. How can I do that?
I'm using .detach() and .attach() for recreating the fragment.
ATTACH
Re-attach a fragment after it had previously been deatched from the UI with detach(Fragment). This causes its view hierarchy to be re-created, attached to the UI, and displayed.
DETACH
Detach the given fragment from the UI. This is the same state as when it is put on the back stack: the fragment is removed from the UI, however its state is still being actively managed by the fragment manager. When going into this state its view hierarchy is destroyed.
HOW I USE IT
getFragmentManager()
.beginTransaction()
.detach(LobbyFragment.this)
.attach(LobbyFragment.this)
.commit();
You can use :
getSupportFragmentManager().beginTransaction()
.replace(R.id.container, LobbyFragment.newInstance()).commit();
To recreate your fragment
Note:getSupportFragmentManager() is if you are using support fragment and AppCompatActivity , if you are using framework fragment class you need to use getFragmentManager()
If you're using Navigation Component, you can use this:
findNavController().navigate(
R.id.current_dest,
arguments,
NavOptions.Builder()
.setPopUpTo(R.id.current_dest, true)
.build()
)
This lets NavController pop up the current fragment and then navigate to itself. You get a new Fragment and fragment ViewModel also gets recreated.
For Kotlin Lover
if you want to recreate fragment you should dettach() fragment then attach() fragment
please follow this step
setp : 1 , first create a method recreateFragment() on your activity class
fun recreateFragment(fragment : Fragment){
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
supportFragmentManager.beginTransaction().detach(fragment).commitNow()
supportFragmentManager.beginTransaction().attach(fragment).commitNow()
}else{
supportFragmentManager.beginTransaction().detach(fragment).attach(fragment).commitNow()
}
}
step : 2 , then call this method on your fragment to recreate this fragment
suppose A Button click then recreate this fragment
button.setOnClickListener {
(activity as yourActivity).recreateFragment(this)
}
If you want to refresh from activity then use:
getSupportfragmentmanager()
.begintransaction
.detach(fragment)
.attach(fragment)
.addtobackstack(null)
.commit();
and if you are in fragment already then use:
public class MyDetailFragment extends Fragment {
....
private void refreshFragment(){
getFragmentManager()
.beginTransaction()
.detach(this)
.attach(this)
.addToBackStack(null)
.commit();
}
...
}
who use Navigation Component !! :
just put a self destination .
<action
android:id="#+id/action_piecesReferenceCount_self"
app:destination="#id/piecesReferenceCount" />
Navigation.findNavController(myview).navigate(R.id.action_piecesReferenceCount_self);
Using the method from Ciardini I got errors sometimes. This works always:
FragmentTransaction ft = getFragmentManager().beginTransaction();
if (Build.VERSION.SDK_INT >= 26) {
ft.setReorderingAllowed(false);
}
ft.detach(this).attach(this).commitAllowingStateLoss();
I had to use 2 transactions for the fragment to reload its content list:
FragmentTransaction ftr = getSupportFragmentManager().beginTransaction();
ftr.detach(localCurrentPrimaryItem)
.commit();
FragmentTransaction ftr2 = getSupportFragmentManager().beginTransaction();
ftr2.attach(localCurrentPrimaryItem)
.commit();
In my case, I had a fragment that needed to be recreated when I clicked on a button, what I did was the following in the onCreateView of the fragment (MyFragmentClass) class:
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
...
Button annuler = (Button) rootView.findViewById(R.id.buttonAnnulerCreation);
annuler.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
getParentFragmentManager().beginTransaction().replace(R.id.fragmentLayout, new MyFragmentClass()).commit();
}
}); }
Define a Kotlin extension function:
/**
* Recreate a fragment without recreating any associated fragment view model. This is useful if initially some work needs
* to be done to set up the data for a fragment. At the start the layout shows a "working" fragment state. When the work completes
* the fragment view model is set to indicate the data is available, and this triggers a different layout to be inflated.
*
* This causes [Fragment.onDestroy] followed by [Fragment.onViewCreated] to be called (but not [Fragment.onCreate]).
*
* For background see [Stackoverflow](https://stackoverflow.com/questions/39296873/how-can-i-recreate-a-fragment)
*/
fun Fragment.recreateFragment() {
val fragment = this
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
parentFragmentManager.beginTransaction().detach(fragment).commitNow()
parentFragmentManager.beginTransaction().attach(fragment).commitNow()
} else {
parentFragmentManager.beginTransaction().detach(fragment).attach(fragment).commitNow()
}
}
I have an issue with fragment navigation in frame-layouts. Namely I cannot find out in which frame the fragment is currently being displayed in.
My setup is the following:
I have 3 parent fragments (PA, PB, PC), each with their own unique framelayouts. Additionally I have 3 Childfragments (CA,CB, CC) and two Grandchildfragments (GA, GB)
The parent fragments call the childfragments into the appropriate framelayouts (works fine), but now I want the childfragments to replace themselves with the appropriate grandchildren, but I have no way of finding out in which framelayout the childfragments are currently displayed in. (I hope that makes sense)
Pseudo-code example:
public class Parent extends Fragment{
//lots of stuff here
public void replaceFragmentWithChild(){
Fragment frg = new childFragment();
FragmentManager mgr = ((FragmentActivity) getActivity()).getSupportFragmentManager();
FragmentTransaction trx = mgr.beginTransaction();
trx.addToBackStack(null);
trx.replace(R.id.content_view_a, childFragment).commit();
}
}
public class Child extends Fragment{
//lots of stuff here
public void replaceFragmentWithGrandChild(){
Fragment frg = new GrandChildFragment();
FragmentManager mgr = ((FragmentActivity) getActivity()).getSupportFragmentManager();
FragmentTransaction trx = mgr.beginTransaction();
trx.addToBackStack(null);
//here is the problem. I would like to call trx.replace(GET-CURRENT-CONTENT-VIEW, frg)
trx.replace(???, frg).commit();
}}
Any input would be much appreciated.
Edit: Ok, I have found a possible solution by saving the ViewGroup Container in the onCreateView and passing that to the trx.replace function.
But unfortunately I am getting the following Message: java.lang.IllegalArgumentException: No view found for id 0x7f0c0057 (de.tel.quenference.activities:id/sara_content_view) for fragment PaperviewFragment{421da470 #2 id=0x7f0c0057} which makes little sense to me, because the view is the same one I am in,or?
What am I missing?
Ok, I figured it out and I sort of want to shoot myself.
First off, the 'No View Found' error stems from the fact, that my parentfragment did not call transaction.addToBackStack (I added it into my example code but not my production code...)
Just putting that in unfortunately did not fix everything, but I had to also change the ParentFragment to not call getChildFragmentManager, but rather the normal FragmentManager
otherwise the grandchildfragment would not appear in the frameLayout.
Basically my working code looks like this:
public class Parent extends Fragment{
//lots of stuff here
public void replaceFragmentWithChild(){
Fragment frg = new childFragment();
FragmentTransaction transaction = getFragmentManager().beginTransaction();
trx.addToBackStack(null);
trx.replace(R.id.content_view_a, childFragment).commit();
}
}
public class Child extends Fragment{
private ViewGroup locContainer;
//lots of stuff here
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
locContainer = container;
return super.onCreateView(inflater, container, savedInstanceState);
}
public void replaceFragmentWithGrandChild(){
Fragment frg = new GrandChildFragment();
FragmentTransaction trx = getFragmentManager().beginTransaction();
trx.addToBackStack(
trx.replace(locContainer.getId(), frg).commit();
}}
The issue I'm having is that I don't know how to get a pointer to a layout within a fragment. It's obvious that to get a layout pointer in Java you would do something like this:
LinearLayout llTemp = (LinearLayout) findViewById(R.id.llTemp)
Something along those lines.
Now what I'm doing is grabbing information from a server in the main class and load a fragment within the same class. I would like to populate the fragment with the information loaded from the outer class. Is there any way to do this? I would have just grabbed the layout from within the fragment and do it that way but I cannot make a reference to it as it's in the fragment.
I'm sure this is a common problem but I couldn't find anything on it specifically like this.
Thanks in advance,
Cheers,
Jake
To Answer the comment:
View view = inflater.inflate(R.layout.main_frag, container, false);
mainLayout = (LinearLayout) view.findViewById(R.id.ll_MainFrag);
return view;
This is what is in my onCreateView.
Okay, just to add how I'm instantiating the Fragment:
private int MAIN = 1;
FragmentManager fm = getSupportFragmentManager();
fragments[MAIN] = new MainFragment();
FragmentTransaction transaction = fm.beginTransaction();
transaction.commit();
getSupportFragmentManager().beginTransaction().add(R.id.flMain, fragments[MAIN]).commit();
From here I would like to be able to do something like:
fragments[MAIN].createTextView();
When creating a Fragment, create public methods to set data:
public class MyFragment extends Fragment {
private TextView text1;
private TextView text2;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = LayoutInflater.from(getActivity()).inflate(R.layout.simple_list_item_2,container,false);
text1 = (TextView) layout.findViewById(R.id.text1);
text2 = (TextView) layout.findViewById(R.id.text2);
return super.onCreateView(inflater, container, savedInstanceState);
}
public void setData(String t1, String t2){
text1.setText(t1);
text2.setText(t2);
}
}
When adding a fragment in parent activity, give it a unique tag:
MyFragment f = new MyFragment();
getFragmentManager().beginTransaction().add(f,"my_fragment").commit();
Later, you can search the fragment from parent activity and call some methods on it:
MyFragment frg = (MyFragment) getFragmentManager().findFragmentByTag("my_fragment");
if(frg != null){
frg.setData("abc","def");
}
Also, if fragment was added from a layout, you can find the fragment by its id.