Android Get View In Fragment Returns Null - android

I have an activity with two fragments, Fragment A and Fragment B. I want to show a view hidden in Fragment A when the user touches a button in fragment B. How can I do this? I have tried to get the whole layout of the activity and get the view but I get a null pointer exception.
My activity layout is as shown below
This is the line I am using. It throws a null pointer exception.
shadowLine = getActivity().findViewById(R.id.shadowLine);
shadowLine.setVisibility(View.VISIBLE);

The simplest way, not the safest:
You can access hosting activity in fragment B by
HostActivity activity =(HostActivity) getActivity();
activity.callOtherFragment();
In that activity, you can access fragment A, by
public void callOtherFragment() {
YourFragment A = (YourFragment)getFragmentManager().findFragmentById(R.id.fragmentA);
A.showSomeStuff();
}
then implement your method in fragment A:
public void showSomeStuff() {
shadowLine.setVisibility(View.VISIBLE);
}

Related

Perform onClick on a view inside a fragment based on bundle param

Problem:
I need to perform onClick on a view present in Fragment depending upon the location from which the activity was launched.
I have setArguments on Bundle in myActivity's onCreate:
final String launchedFrom = getIntent().getStringExtra(LAUNCHED_FROM);
if (launchedFrom!=null) {
bundle.putBoolean("myStr", true);
}
Now, I fetch the argument from onCreateView of Fragment and perform click on 3rd item in the list:
if (this.getArguments().getBoolean("myStr")) {
mOptions.getAdapter().getView(3, null, null).callOnClick();
}
This opens the 3rd item on the list. Now, when I press back button the onCreateView is getting called again and the control is redirected to the 3rd item in the list as the bundle arguments have not changed on backPress.
I want to navigate back to the place from where the activity was called when I press the back button.
This is the code that is called when using .callonClick()
#Override
public void onViewSelected(List<Entry> faqs) {
Fragment fragment = FragmentB.newInstance(new ArrayList<>(faqs));
getFragmentManager().beginTransaction()
.replace(R.id.activity_frame_layout, fragment)
.addToBackStack(FragmentB.TAG)
.commit();
}
Solution#1: Not the best approach, if I move the code of onCreateView to onCreate, but I think there needs to be a better approach to solve this.

Adding a listener between two fragments

I have two fragments, A and B let's say, where B contains a list. I would like to add a listener on Fragment B that notifies Fragment A of the chosen list item. I couldn't figure out how to initialize the listener in Fragment B since it is bad practice to pass arguments in fragment's constructors.
NOTE: Fragment B is contained inside Fragment A. i.e. I have a FrameLayout in Fragment A; and Fragment B covers that FrameLayout.
Any idea how I could do that?
If you're saying that Fragment B is a child fragment of Fragment A (that is, you've added it to Fragment A using Fragment A's getChildFragmentManager()), then you can use the same approach that you use for Activity interfaces, but using getParentFragment() instead of getActivity().
For example:
Fragment B:
#Override
public void onAttach(Context context) {
MyInterface myInterface = (MyInterface) getParentFragment();
}
Assuming that Fragment A implements MyInterface.
One convenience method we've used to avoid having to know whether a Fragment is hosted by another Fragment or an Activity is something like:
public static <T> getInterface(Class<T> interfaceClass, Fragment thisFragment) {
final Fragment parent = thisFragment.getParentFragment();
if (parent != null && interfaceClass.isAssignableFrom(parent)) {
return interfaceClass.cast(parent);
}
final Activity activity = thisFragment.getActivity();
if (activity != null && interfaceClass.isAssignableFrom(activity)) {
return interfaceClass.cast(activity);
}
return null;
}
Then you can just use:
MyInterface myInterface = getInterface(MyInterface.class, this);
and it doesn't matter whether Fragment B is hosted as a child Fragment or in an Activity directly.
A better approach for this situation, since what you want to do is communication between fragments, is to use an interface. You want to notify A when B has changed. This should be done through the parent activity. Here is the android documentation on the topic: https://developer.android.com/training/basics/fragments/communicating.html.
The gist of it is that you want to define an interface with a method called OnItemSelected (you can name it whatever you want). In B, you want a reference to this interface. When an item is selected, call your new OnItemSelected method. Implement this interface in the parent activity of the two fragments. In the implementation, you can put whatever code you want to modify A.
An example
CommunicationInterface
public interface CommunicationInterface {
public void onItemSelected(int position);
}
FragmentB
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
CommunicationInterface myInterface = (CommunicationInterface) getActivity();
// What ever else you want here
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity
myInterface.onItemSelected(position);
}
MainActivity
public class MainActivity extends FragmentActivity implements CommunicationInterface {
// What ever other code you have
#Override
public void onItemSelected(int position) {
FragmentA fragA = (FragmentA)
getSupportFragmentManager().findFragmentById(R.id.fragment_a);
// Code to interact with Fragment A
}
Checkout the contract pattern https://gist.github.com/JakeWharton/2621173
If you are using multiple fragment, you dont have do it for every fragment, just add it to your BaseActivity if you have one.
This example shows the communication between activity and fragment. But for nested fragment you can replace the acitivy with getParentFragment();

Unable to access layout inflater as child view has not initialised

I have an Android activity that searches a web service for new content to display then either displays a NoResultFragment or a ResultFragment which represents a swipe stack for the user to swipe through the items returned. Because I need to manage the stack, retrieving more data in the background as the stack gets low etc from the Activity, all of the stack details are held at the activity level and the yes/no actions trigger methods on the activity. All good so far.
The problem is I am using the layout inflater in the ResultFragment class to generate dynamic child Views, each one of which represents an item on the stack. These then get returned to the Activity controller which manages them, sends them to the fragment to display, hides them, moves them around etc, so I need access to the child item Views from the activity to do all this. I need to generate the actual child views from within the ResultFragment though, as that is where they will be visually displayed.
I create the ResultFragment, set it to the content area and then try and generate the child views by calling into the fragment created. The error is that the onViewCreate() method has not yet been called on the ResultFragment class as it has only just been added to the content frame, so there is no layoutinflater and my method to return the child View fails. I get the feeling there is something off with my design here, can someone shed some light on how to do this? Is it as simple as just passing through the parent layoutinflater from the Activity class?
Child view creation method
public View getChildView(StorySeed seed, int seedIndex)
{
final View m_view = inflater.inflate(R.layout.fragment_item, null); // Code to populate the view
return m_view;
}
activity method
private void initialiseResults(ArrayList<StorySeed> storySeeds) {
resultsFragment = new ResultsFragment(storySeeds, getApplicationContext());
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.content_frame, resultsFragment)
.commit();
// load the first results to screen
seedIndex = 1;
for (int i = 0; i < seedsToDisplay; i++) {
getNextToStack();
}
}
It is the call to getNextToStack() that is going into the Fragment class and calling the getChildView() method
I would suggest that you create the views in the activity (the controller) and pass them to the fragment as needed. The fragment is your MVC "view" and it should only tell the controller what happened. The controller decides what to do after that.
The way you can have one fragment replace itself by another is to call a method on the activity. Here's a quick example:
interface IAppController {
void onResultsNotFound();
}
class MyActivity extends Activity implements IAppController{
....
public void onResultNotFound(){
//switch fragments
}
}
class MyFragment {
....
void myMethod(){
IAppController controller = (IAppController) getActivity();
controller.onResultsNotFound();
}
}
Hope this helps

fragment.getView() return null after backpressed

I try to change textview under attached fragment on activity so i stored attachedFragment as variable as code below
#Override
public void onAttachFragment(android.support.v4.app.Fragment fragment) {
super.onAttachFragment(fragment);
attachedFragment = fragment;
}
then when a button is clicked I call following code
if(attachedFragment != null && attachedFragment.getView() != null)
{
TextView tvGender = (TextView) attachedFragment.getView().findViewById(R.id.tv_gender);
if(tvGender!=null)
tvGender.setText(R.string.title_step_one_gender);
}
When I start the activity and it works fine until i changed into the next fragment and pressed back; the attachedFragment.getView() always returns null
My question:
How is it possible it returns null while the first time is okay?
What is the best solution to change textview/any other control
within fragment? There are lots of fragments and I only need to
change attached fragment.
nb: All code above are under main activity
Please correct me if I misunderstood your question. It sounds like your situation is, you attach fragment A, then you attach fragment B, then you press back, leaving you with fragment A. In this case, attachedFragment is just a variable, so it continues to point to B, but since B is now detached, it is null. Pressing back will not repopulate the variable attachedFragment with fragment A.
Try using findFragmentById or findFragmentByTag. Check out this thread for more info.

'IllegalStateException: Activity has been destroyed' when 'getSupportFragmentManager()' called after Activity restart

I have a parent Fragment Activity that has a ViewPager which contains a child ViewPager. The child ViewPager contains Fragments for each page. I communicate between these child page fragments and the top parent Fragment Activity using a callback interface e.g.
public interface Callbacks {
public void onItemSelected(Link link);
}
In the parent Fragment Activity I listen for onItemSelected events e.g.
#Override
public void onItemSelected(Link link) {
Bundle argumentsFront = new Bundle();
argumentsFront.putParcelable(FragmentComments.ARG_ITEM_ID, link);
fragmentComments = new FragmentComments();
fragmentComments.setArguments(argumentsFront);
getSupportFragmentManager().beginTransaction().replace(R.id.post_container, fragmentComments).commitAllowingStateLoss();
}
Now this works fine when the app is first launched.
If you turn the device to change the orientation the Activity restarts. All fragments reinitialise themselves as I use setRetainInstance(true); (I do not call setRetainInstance(true) in the page Fragments of the child ViewPager as it is not supported). However if I click a list item in the Fragment of the child ViewPager I get this exception:
FATAL EXCEPTION: main
java.lang.IllegalStateException: Activity has been destroyed
at android.support.v4.app.FragmentManagerImpl.enqueueAction(FragmentManager.java:1342)
at android.support.v4.app.BackStackRecord.commitInternal(BackStackRecord.java:595)
at android.support.v4.app.BackStackRecord.commitAllowingStateLoss(BackStackRecord.java:578)
Does anyone know why this happens?
Thanks
When you rotate the device, Android saves, destroys, and recreates your Activity and its ViewPager of Fragments. Since the ViewPager uses the FragmentManager of your Activity, it saves and reuses those Fragments for you (and does not create new ones), so they will hold the old references to your (now destroyed) original Activity, and you get that IllegalStateException.
In your child Fragments, try something like this:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
Log.v(TAG, "onAttach");
// Check if parent activity implements our callback interface
if (activity != null) {
try {
mParentCallback = (Callbacks) activity;
}
catch (ClassCastException e) {
}
}
}
Then when a selection occurs:
if(mParentCallback != null) {
mParentCallback.onItemSelected(selectedLink);
}
Since onAttach gets called as part of the Fragment lifecycle, your Fragments will update their callback reference on rotation.
I had a similar issue, I think it is because the fragments are retained and are keeping a reference to a destoryed activity, my solution was to keep a reference to the fragment in the activity e.g Fragment myfragment = null. And then use the following code in MyFragment:
public void onAttach(Activity activity) {
super.onAttach(activity);
((TestActivity)activity).contentFragment = this;
}
Had a similar issue. Basically if the ViewPager just has couple of fragments, then store references to them in current activity. DO NOT call pagerAdapter's getItem() because it creates a new fragment and it is not attached to any activity and that's why we see "Activity has been destroyed" exception. If you don't want to keep fragment references, then you can use findViewWithTag() method to get Fragment object.
Committing transactions in OnPostResume callback fixed the issue for me. Thanks to following blogpost
http://www.androiddesignpatterns.com/2013/08/fragment-transaction-commit-state-loss.html
#Override
protected void onPostResume() {
super.onPostResume();
// Commit your transactions here.
}
I had this problem with nested fragments and none of the stackoverflow solutions worked for me. Just it seems, that there is a bug with support library, when dismissed fragments still store pointers to previous activity (so getFragmentManager() just returns null, because it is called on already destroyed activity), that's why you need to manage pointers yourself. I ended up with a following solution:1. In the first level fragment I was saving pointer to the activity in the method
public void onAttach(Activity activity) {
super.onAttach(activity);
parentActivity = activity; // parentActivity is static variable
}
2. In the activity which handles fragments I ended up with this code:
private void launchFragment(Fragment fragment, Activity parent) {
FragmentTransaction transaction;
if(parent == null)
transaction = mFragmentManager.beginTransaction();
else // for nested child fragments, workaround for Android parent pointer bug
transaction = parent.getFragmentManager().beginTransaction();
transaction.replace(R.id.container, fragment);
transaction.addToBackStack(null);
transaction.commit();
}
You should pass parentActivity of FIRST level fragment only when you are calling SECOND level (nested) fragments, as it seems that this bug is only with nested ones after you bring your app from foreground.

Categories

Resources