how to access fragments elements in MainActivity()? - android

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()
}
}

Related

Executing lifecycle method of backstack fragment problem in android

I have an activity with two fragment and want to be executed first fragment when its back from second fragment using back button. And i am using the add() when navigating first fragment to second fragment. Here is my scenario and code snippet:
First fragment:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
// Inflate the layout for this fragment
View view = inflater.inflate(R.layout.one_fragment, container, false);
final Button button = (Button) view.findViewById(R.id.buttonChange);
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
buttonClicked(v);
}
});
return view;
}
public void buttonClicked(View view) {
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fragment_Container, new TwoFragment());
fragmentTransaction.addToBackStack("sdfsf");
fragmentTransaction.commit();
}
Moving to Second fragment and here is the code:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.two_fragment, container, false);
return view;
}
The problem is that, When I am navigating from first to second fragment and then back again in the first fragment using back button first fragment lifecycle method is not executing. Instead of using add() if I use replace() then lifecycle method are executing properly. I know its the difference between add() and replace() but I want to use add() and also want to have navigation callback to handle some logic when I back in the first fragment using back button.
Also tried below code:
fragmentManager.addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
Log.e(TAG, "onBackStackChanged: ");
// Update your UI here.
}
});
But its also calling multiple times and creating anomalies.
How can I handle this? Specially handle some logic in first fragment when I back from second fragment.
The easiest way I can think of is to set result when you're done with the second fragment that essentially tells the first fragment to "resume" via its onActivityResult method.
When you create an instance of Fragment B, call #setTargetFragment() and pass in Fragment A as your target fragment. Then when Fragment B is done and going to return to Fragment A, before it exits, you will set the result of it for Fragment A by calling:
getTargetFragment().onActivityResult(getTargetRequestCode(), RESULT_FRAGMENT_B_FINISHED,null)
///// horizontal scroll padding
Note that RESULT_FRAGMENT_B_FINISHED would be some static integer you define somewhere, like
public static final int RESULT_FRAGMENT_B_FINISHED = 123123;
Now in Fragment A all you need to do is override onActivityResult and check that the request code matches the request code integer from setTargetFragment and the result code also matches RESULT_FRAGMENT_B_FINISHED, if so you can run the code that would have been fired from onResume().
#getTargetFragment()
#onActivityResult()
#getTargetRequestCode()
Instead of passing data between the two fragments I recommend you to use a SharedViewModel.
The idea is that the first fragment observe some data for changes and the second one edit this data.
Example:
Shared ViewModel
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new
MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
First fragment
public class FirstFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model =
ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// Update the UI.
});
}
}
Second fragment
public class SecondFragment extends Fragment {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedViewModel model =
ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.select(new Item("value 1","value 2");
}
}
You can read about ViewModels, LiveData and Architecture components starting from here: https://developer.android.com/topic/libraries/architecture/viewmodel#java
This answer is assuming that you want to execute some logic based on some data change. If it's not the case, you can explain what kind of logic do you want to execute and I will edit my answer.

findFragmentById and findFragmentByTag both return null

I am trying to implement a card flip animation with fragments that have widgets in them, so I need to get access to the fragments, but I am not succeeding.
I can attach the fragments to the placeholder view in the activity and flip back and forth between two fragments, but cannot get access to the fragments.
I have tried adding the fragment with and without a tag and either way, I always get a null response from findFragmentById or findFragmentByTag.
Here are my code snippets - Thanks in advance for any insight.
File - activity_line.xml
...
<FrameLayout
android:id="#+id/card_placeholder_view" />
...
File - LineActivity.java
import android.app.Fragment;
public class LineActivity extends Activity {
...
ControlFragment controlFragment;
public static class CardFrontFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_control, container, false);
}
}
#Override
public void onCreate {
setContentView(R.layout.activity_line);
getFragmentManager()
.beginTransaction()
.add(R.id.card_placeholder_view, new CardFrontFragment(), "fragment_tag")
.commit();
controlFragment = (ControlFragment)getFragmentManager().findFragmentByTag("fragment_tag");
controlFragment = (ControlFragment)getFragmentManager().findFragmentById(R.id.card_placeholder_view);
}
}
The problem is that you are calling findFragmentByTag() and/or findFragmentById() immediately after commiting a FragmentTransaction.
Fragment transactions are, by their very nature, asynchronous. You've told the system that you want it to add your fragment, and it will... but it won't necessarily do it instantaneously (though sometimes it might).
You could make the transaction synchronous by replacing .commit() with .commitNow() or .commitNowAllowingStateLoss(), but generally I don't recommend doing that. I think you're better off just embracing the fact that the transaction is async.
Move any code that depends on the results of findFragmentByTag() or findFragmentById() out of onCreate() and into onResumeFragments() (or into the Fragments themselves).
#Override
protected void onResumeFragments() {
super.onResumeFragments();
controlFragment = (ControlFragment)getFragmentManager().findFragmentByTag("fragment_tag");
controlFragment = (ControlFragment)getFragmentManager().findFragmentById(R.id.card_placeholder_view);
}
Edit
I just realized you're extending Activity and not AppCompatActivity, so onResumeFragments() doesn't exist for you. Instead, you could use onAttachFragment():
#Override
public void onAttachFragment(Fragment fragment) {
super.onAttachFragment(fragment);
if (fragment instanceof ControlFragment) {
controlFragment = (ControlFragment)fragment;
}
}

Communicating between fragments within an activity

I am trying to write a test app which contains an activity. There are two fragments inside this activity, which are defined as LeftFragment and RightFragment. I used getFragmentManager().findFragmentById() to get connection from each other fragments. By using that methode I am able to get an LeftFragment object from RightFragment, but not RightFragment object from LeftFragment. It just works only oneway. I am doing this, because I want to call some operations from other fragment, that return some values. I was thinking about using EventBus but I failed too. How can I achive that?
Here is my LeftFragment
public class LeftFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
RightFragment rightFragment = (RightFragment) getFragmentManager().findFragmentById(R.id.rightFragment);
if (rightFragment != null){
makeToast(rightFragment.getMessageFromRight());
}else {
makeToast("does not found rightFragment");
}
return inflater.inflate(R.layout.fragment_left, container, false);
}
public String getMessageFromLeft(){
return "Hi! Im left";
}
private void makeToast(String text){
Toast.makeText(getContext(),text,Toast.LENGTH_SHORT).show();
}
}
And here is my RightFragment
public class RightFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
LeftFragment leftFragment = (LeftFragment) getFragmentManager().findFragmentById(R.id.leftFragment);
if (leftFragment != null){
makeToast(leftFragment.getMessageFromLeft());
}else {
makeToast("does not found leftFragment");
}
return inflater.inflate(R.layout.fragment_right, container, false);
}
public String getMessageFromRight(){
return "Hi! Im right!";
}
private void makeToast(String text){
Toast.makeText(getContext(),text,Toast.LENGTH_SHORT).show();
}
}
There are many ways to communicate between 2 fragments . If 2 fragments loaded at the same time. I usaually use one of 2 ways below to do it.
You can use this link using obserable pattern to communication 2 fragments.
you can use EventBus lib for communication, it 's very simple
Your issue:
By using that methode I am able to get an LeftFragment object from
RightFragment, but not RightFragment object from LeftFragment
I think your problem is LeftFragment is intitialized previous, so you can find it from RightFragment. Your solution is ok, using EventBus. YOu need to review your codes to find the issue. You can test by creating other methods, after 2 fragment was initialized.
For ex: click button in LeftFragment, toast a message in RightFragment.
Probably what is happening is that the Left Fragment is getting the OnCreateView() call first, at which point the Right Fragment has not been inflated yet (therefore it can't be "found" by findFragmentbyId()).
I would suggest moving the code that gets the references to the other fragments into onStart(), and only inflate the fragments in onCreateView().

How to know if a Fragment already called onCreateView()

We all know that when using ViewPager with Fragment and FragmentPagerAdapter we get 3 Fragment loaded: the visible one, and both on each of its sides.
So, if I have 7 Fragments and I'm iterating through them to see which 3 of them are the ones that are loaded, and by that I mean onCreateView() has already been called, how can I determine this?
EDIT: The Fragment doesn't have to be the one that the ViewPager is showing, just that onCreateView() has already been called.
Well logically, this would be a reasonable test if onCreateView has been called:
myFragment.getView() != null;
Assuming you a have a reference to all of the fragments in the pager iterate, them and check if they have a view.
http://developer.android.com/reference/android/app/Fragment.html#getView()
Update
The above answer assumes that your fragments always create a view, and are not viewless fragments. If they are then I suggest sub classing the fragment like so:
public abstract class SubFragment extends Fragment
{
protected boolean onCreateViewCalled = false;
public boolean hasOnCreateViewBeenCalled()
{
return onCreateViewCalled;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup Container, Bundle state){
onCreateViewCalled = true;
return null;
}
}
Just bear in mind that further sub classes will have to call super or set the flag themselves should they override onCreateView as well.
I added an interface to Fragment. Looks like:
protected OnCreateViewCallback createViewCallback = null;
public void setCreateViewCallback(OnCreateViewCallback createViewCallback) {
this.createViewCallback = createViewCallback;
}
public interface OnCreateViewCallback {
void onCreateView();
}
In my onCreateView():
//initialize your view.
if (createViewCallback != null) {
createViewCallback.onCreateView();
createViewCallback = null;
}
return mainView;
From my activity:
if (ocrFragment.getView() == null) {
ocrFragment.setCreateViewCallback(new MainScreenFragment.OnCreateViewCallback() {
#Override
public void onCreateView() {
ocrFragment.ocrImage(picture, false);
}
});
} else {
ocrFragment.ocrImage(picture, false);
}
If you are trying to perform something after onCreateView is called, use onViewCreated:
Called immediately after onCreateView(LayoutInflater, ViewGroup,
Bundle) has returned, but before any saved state has been restored in
to the view. This gives subclasses a chance to initialize themselves
once they know their view hierarchy has been completely created. The
fragment's view hierarchy is not however attached to its parent at
this point.
public void onViewCreated(View view, Bundle savedInstanceState) {
MyActivity myActivity = (MyActivity) getActivity();
MyActivity.newAsyncTask(mPar);
}
You could also check for Fragment.isVisible() because a Fragment is in visible state when it's in the offscreen page limit of a ViewPager.
Edit: But it just really depends on what you really want to achieve with your question. Perhaps some kind of update to all UIs in your Fragments when their UI is ready?
EDIT:
Just another addition, you could listen to onViewCreated() and set a flag. Or notify your Activity and do further work (getActivity() will return your Activity at this point). But really, better state what you want to accomplish with your question.

How to replace a Fragment on button click of that fragment?

I have an activity containing multiple fragments. Activity initially have fragment and in it have two buttons. Upon clicking this button I have to replace the fragment by new fragment. Each fragment has various widgets and replace the current fragment as various events.
This is my problem. How can I achieve this?
Suggest me ideas.
you can replace fragment by FragmentTransaction.
Here you go.
Make an interface.
public interface FragmentChangeListener
{
public void replaceFragment(Fragment fragment);
}
implements your Fragment holding activity with this interface.
public class HomeScreen extends FragmentActivity implements
FragmentChangeListener {
#Override
public void replaceFragment(Fragment fragment) {
FragmentManager fragmentManager = getSupportFragmentManager();;
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(mContainerId, fragment, fragment.toString());
fragmentTransaction.addToBackStack(fragment.toString());
fragmentTransaction.commit();
}
}
Call this method from Fragments like this.
//In your fragment.
public void showOtherFragment()
{
Fragment fr=new NewDisplayingFragment();
FragmentChangeListener fc=(FragmentChangeListener)getActivity();
fc.replaceFragment(fr);
}
Hope this will work!
NOTE: mContainerId is id of the view who is holding the fragments inside.
You should override Fragment's onString() method as well.
Well even I am learning android...
I solved same problem recently, "How to Change Fragment On button's click event".
buttonName.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentTransaction fragmentTransaction = getActivity()
.getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame1, new Homefragment());
fragmentTransaction.commit();
}
});
Here frame1 is id of FrameLayout which have define in my DrawerLayer's XML.
So now whenever I want fragment transaction I use this code. Each time it will replace frame1 instated of your last fragment.
FragmentTransaction fragmentTransaction = getActivity()
.getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.frame1, new newfragment());
fragmentTransaction.commit()
Hope this will help..
You can use the following to replace a fragment on button click of that fragment:
getFragmentManager().beginTransaction().replace(R.id.main_content, new insertFragmentNameHere()).addToBackStack(null).commit();
Define an interface and call it IChangeListener (or something like that) and define a method inside which will do the work (ex, changeFragment()) (or call another method which will do the work) for changing the fragment).
Make your activity implement the interface, or make an anonymous class within the activity implement it.
Make a paramerized constructor of your fragment, which accepts a IChangeListener parameter.
When initializing the fragment, pass your IChangeListener implementation (the anonymous class or the activity, implementing it)
Make the onClick listener of the button call the changing method of the IChangeListener.
From the future 2017 and after, there exists different libraries that triggers Events using Bus and now you can use it to tell the activity when an event is trigger in a fragment that it owns.
Refer to:
RxBus with RxJava
You can check new architectures suggested by Google
ViewModel
Don't use the approach in the accepted answer, get really ugly with more than 3 different events from fragments
you can try this code it's work fine with me , inflate the layout to view , Define the buton and on click ,
Button btn_unstable;
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_home,container,false);
btn_unstable = (Button)view.findViewById(R.id.btn_unstable);
btn_unstable.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
getActivity().getSupportFragmentManager().beginTransaction().replace(R.id.fragment_replace, new UnstableFragment()).commit();
}
});
return view;
}

Categories

Resources