I'd like to know how to access a view (that is in an activity) from a fragment.
More specifically, a view starts in the activity and I want the same view to to end in the fragment. Thanks.
In Activity:
private View layoutProgress;
protected void onCreate(Bundle savedInstanceState){
layoutProgress = findViewById(R.id.layout_progress);
layoutProgress.setVisibility(View.GONE);
}
when the button is pressed:
fragment.getInstance().printScreen();(function in fragment)
layoutProgress.setVisibility(View.VISIBLE);
In Fragment:
Execute the code and i'd like to set layoutProgress visibility gone
In fragment:
Activity act = getActivity();
if (act == null) return;
View layoutProgress = findViewById(R.id.layout_progress);
But really, better design would be to call a method in your Activity that will hide the ProgressBar -- as to keep all interaction with that view in one place and not get entangled in all the states if you decide to do more stuff with that progress bar.
Read the guide on communication between Fragments for a proper (as in, future error-resistant) implementation of calling Activity's method from a Fragment.
make your progressview global in activity
and below to access progressview in fragment
((Youractivity) getActivity()).layoutProgress.setVisibility(View.GONE);
Related
i am trying to add shared element transition into my app.
Scenario is that user clicks on image thumbnail which than opens another activity with full screen image view.
This works fine if shared view is hosted directly within layout of target activity. Works smoothy for enter/exit animation.
But when i'am trying to achieve similar effect within fragment which is nested in target activity this approach doesn't work. Funny thing is that enter animation is not showed, but exit animation is working fine .
Another even more complicated view hierarchy is that if target view (ImageView) is hosted within view pager which is hosted in frame layout of target activity.
Does someone had same issue ?
Edit:
My click listener code
public class OnClickPicture extends OnClickBase {
private ObjectPicture object;
public OnClickPicture(Activity_Parent activity, ObjectPicture object) {
super(activity);
this.object = object;
}
public void onClick(View v) {
picasso.load(object.getFullUrl()).fetch();
Intent intent = new Intent(activity, ActivityPicture.class);
intent.putExtra("picture_object", helper.gson.toJson(object));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && v != null) {
Pair<View, String> p1 = Pair.create(v, "image");
ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation(activity, p1);
activity.startActivity(intent, options.toBundle());
} else {
activity.startActivity(intent);
}
}
}
The way that transitions work require the new Activity to be created, measured and laid out before any animations can happen. That's so that it can find the view that you want to animate and create the appropriate animation.
In your case this isn't happening because, as stated in the docs, all FragmentTransaction.commit() does is schedule work to be done. It doesn't happen immediately. Therefore when the framework creates your Activity it cant find the view that you want to animate. That's why you don't see an entry animation but you do see an exit animation. The View is there when you leave the activity.
The solution is simple enough. First of all you can try FragmentManager.executePendingTransactions(). That still might not be enough. The transitions framework has another solution:
In the onCreate of Activity postponeEnterTransition(). This tells the framework to wait until you tell it that its safe to create the animation. That does mean that you need to tell it that its safe (via calling startPostponedEnterTransition()) at some point. In your case that would probably be in the Fragments onCreateView.
Here's an example of how that might look like:
Activity B
#Override
protected void onCreate(Bundle savedInstanceState) {
// etc
postponeEnterTransition();
}
Fragment B
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View sharedView = root.findViewById(R.id.shared_view);
sharedview.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
sharedview.getViewTreeObserver().removeOnPreDrawListener(this);
getActivity().startPostponedEnterTransition();
return true;
}
});
}
Thanks to Alex Lockwood for his detailed blog posts about the Transitions framework.
I have written android app now for a long time but now I'm facing a problem that I have never thought about. It is about the android lifecycle of Activitys and Fragments in in relation to configuration changes. For this I have create a small application with this necessary code:
public class MainActivity extends FragmentActivity {
private final String TAG = "TestFragment";
private TestFragment fragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
fragment = (TestFragment) fm.findFragmentByTag(TAG);
if (fragment == null) {
fragment = new TestFragment();
fm.beginTransaction().add(R.id.fragment_container, fragment, TAG).commit();
}
}
}
And here is my code for the TestFragment. Note that I'm calling setRetainInstance(true); in the onCreate method so the fragment is not recrated after a configuration change.
public class TestFragment extends Fragment implements View.OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
View rootView = li.inflate(R.layout.fragment_test, parent, false);
Button button = (Button) rootView.findViewById(R.id.toggleButton);
button.setOnClickListener(this);
return rootView;
}
#Override
public void onClick(View v) {
Button button = (Button) v;
String enable = getString(R.string.enable);
if(button.getText().toString().equals(enable)) {
button.setText(getString(R.string.disable));
} else {
button.setText(enable);
}
}
}
And here is the layout that my fragment is using:
<LinearLayout
...>
<EditText
android:id="#+id/editText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="#+id/toggleButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/enable"/>
</LinearLayout>
My problem is that if I rotate the device the text of the Button change back to the default value. Of course the View of the Fragment is new created and inflated but the saved instance for the Views should be restored. I also have an EditText in my layout and there the text and other properties remains after the rotation. So why is the Button not restore from the Bundle by default? I have read on the developer site:
By default, the system uses the Bundle instance state to save information about each View object in your activity layout (such as the text value entered into an EditText object). So, if your activity instance is destroyed and recreated, the state of the layout is restored to its previous state with no code required by you.
I've also read a lot of answers the last days but I do not know how actual they are anymore. Please do not leave a comment or an answer with android:configChanges=... this is very bad practice. I hope someone can bring light into my lack of understanding.
You should save state of your fragment in the onSaveInstanceState(Bundle outState) and restore it in the onViewCreated(View view, Bundle savedState) method. This way you will end up with the UI just as it was before configuration change.
TextView subclasses don't save their text by default. You need to enable freezesText="true" in the layout, or setFreezesText(true) at runtime for it to save its state.
As per documentation, views should maintain their state without using setRetainInstance(true). Try to remove it from your onCreate, this should force the fragment to be recreated on screen rotation, hence all of it's views should be saved before rotation and restored after.
This stack overflow should answer your question:
setRetainInstance not retaining the instance
setRetainInstance does tell the fragment to save all of its data, and for ui elements where the user has manipulated the state (EditText, ScrollView, ListView, etc) the state gets restored. That being said, normal read-only UI components get reinflated from scratch in onCreateView and have to be set again - my guess would be that their properties are not considered "data" that needs to be retained and restored - Google probably does this for performance reasons. So things like a normal Button, ImageView, or TextView need their contents set manually when they are reinflated if it differs from the initial state in the XML. (TextView's android:freezesText basically puts the TextView in a mode that uses an implementation to save it's state and restore it).
PS: According to this stack overflow Save and restore a ButtonText when screen orientation is switched, you may be able to set android:freezesText on the button to have it keep the text you set - I haven't tried it, but it makes sense.
Edit after Op feedback
Although this fails to answer the question, after consultation with the OP, I've decided to leave it here as a reference point of information on the topic for people who land here. Hope you find it helpful.
Try putting your setRetainInstance in onCreateView. See here
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater li, ViewGroup parent, Bundle bundle) {
setRetainInstance(true);
View rootView = li.inflate(R.layout.fragment_test, parent, false);
Button button = (Button) rootView.findViewById(R.id.toggleButton);
button.setOnClickListener(this);
return rootView;
}
Called when the fragment's activity has been created and this
fragment's view hierarchy instantiated. It can be used to do final
initialization once these pieces are in place, such as retrieving
views or restoring state. It is also useful for fragments that use
setRetainInstance(boolean) to retain their instance, as this
callback tells the fragment when it is fully associated with the new
activity instance. This is called after onCreateView(LayoutInflater,
ViewGroup, Bundle) and before onViewStateRestored(Bundle).
developer.android.com/reference/android/app/Fragment.html#onActivityCreated
Control whether a fragment instance is retained across Activity
re-creation (such as from a configuration change). This can only be
used with fragments not in the back stack. If set, the fragment
lifecycle will be slightly different when an activity is recreated:
onDestroy() will not be called (but onDetach() still will be, because
the fragment is being detached from its current activity).
onCreate(Bundle) will not be called since the fragment is not being
re-created.
onAttach(Activity) and onActivityCreated(Bundle) will
still be called.
developer.android.com/reference/android/app/Fragment.html#setRetainInstance
And taken from here:
onCreate : It is called on initial creation of the fragment. You do
your non graphical initializations here. It finishes even before the
layout is inflated and the fragment is visible.
onCreateView : It is called to inflate the layout of the fragment i.e
graphical initialization usually takes place here. It is always called
sometimes after the onCreate method.
onActivityCreated : If your view is static, then moving any code to
the onActivityCreated method is not necessary. But when you - for
instance, fill some lists from the adapter, then you should do it in
the onActivityCreated method as well as restoring the view state when
setRetainInstance used to do so. Also accessing the view hierarchy of
the parent activity must be done in the onActivityCreated, not sooner.
Let me know if this helps.
I have a problem when trying to access my dialogs fragments view.
Here is what I do:
For each button in my main activity I create a new dialogFragment when the button is clicked:
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
DialogFragment newFragment = ResAmountDialog.newInstance(R.string.res_dialog_title);
newFragment.show(getFragmentManager(), "dialog");
ImageView resIcon = (ImageView) newFragment.getView().findViewById(R.id.resourceIcon1);
resIcon.setImageResource(currentResourceImage);
}
});
The dialog fragment consists of an EditText and ImageView. I'm trying to access the imageView in the above code by getting the fragment, then its view and then finding it by ID, but it returns null.
I tried to find a solution on the internet but all the similar problems had their views accessed within the fragment, i try to access it from outside.
A Fragment's view is not loaded until its onCreateView() method is invoked (see the Fragment lifecycle: http://developer.android.com/guide/components/fragments.html). You really shouldn't try to update a Fragment's view like you are trying to. Set the image inside your Dialog Fragment's onCreateView() instead. Use setArguments() and getArguments() to pass a specific resource ID (see passing argument to DialogFragment).
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
As the headline says: Is it possible to get the currently visible fragment with all UI elements initialized within the onCreate() method of the activity?
I am implementing a separation into model, view and controller with separate controller classes that handle business logic and UI events. Therefore they need a reference to the current fragment. These controllers are initialized in the onCreate() method of the activity hence I need the initialized fragment within that method.
I welcome any kind of advice :)
EDIT:
Adding some code for better understanding:
I'm using dagger for dependency injection and would like to do this in the onCreate() method. As I said before my controller needs an the mapView element. And that is why I would like to have a fragment with the mapView element initialized.
MapActivity#onCreate(Bundle):
public void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.activity_layout);
MyMapFragment fragment = new MyMapFragment();
getFragmentManager().beginTransaction()
.add(R.id.activity_container, fragment, "fragment")
.commit();
ObjectGraph.create(new Module(fragment.getMapView())).inject(this);
}
activity_layout.xml
<android.support.v4.widget.DrawerLayout>
<FrameLayout
android:id="#+id/activity_container"
... />
<ListView .../>
</android.support.v4.widget.DrawerLayout>
fragment_layout.xml
<RelativeLayout>
<org.osmdroid.map.MapView
android:id"#+id/mapview"
... />
<Button .../>
</RelativeLayout>
2ND EDIT:
So it seems like that is not possible... Yay for the downvote ^^
By default, no. At the time activity onCreate() runs, the fragment is not attached to the activity yet.
Right place to access a fragment's views is in the fragment itself. Consider putting the controller assignments in the fragment within its lifecycle such as onCreateView() or onViewCreated().
It is possible to explicitly run queued up fragment transactions using executePendingTransactions(), or implicitly after super.onStart() has been run in the activity lifecycle. After that the fragment views are accessible in the activity view hierarchy.
in your onCreate method add the following (I used a textview as an example):
while (fragment.getView() == null) {
}
rootView = fragment.getView();
TextView myView = (TextView) rootView.findViewById(R.id.text_view);
Make sure to return the rootView in your fragment's onCreateView method as follows:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_activity,
container, false);
return rootView;
}
I know that the idea of getting your fragment to access its views from the main activity is bad. Yet, this solution may give you what you want.