View reuse in fragments android - android

I am trying to save my View states in my fragment but I am concerned I make be leaking my Activity. Here is what I am doing:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle state){
if(mView != null){
View oldParent = mView.getParent();
if(oldParent != container){
((ViewGroup)oldParent).removeView(mView);
}
return mView;
}
else{
mView = inflater.inflate(R.id.fragview, null)
return mView;
}
}
I am concerned because I know all Views hold onto a context and I don't know if it is the Activity context or Application context if inflated from the inflater. Perhaps it would be a better idea to pragmatically create the view and set its attributes using getActivity().getApplication() rather than use the inflater. I would appreciate any feedback on this.
Thanks!
EDIT: Confirmed Activity leak, although this code works great don't do it :*(

I am trying to save my View states in my fragment but I am concerned I make be leaking my Activity.
Use onSaveInstanceState() (in the fragment) and onRetainConfigurationInstance() (in the activity) for maintaining "View states" across configuration changes. I am not quite certain what other "View states" you might be referring to.
I am concerned because I know all Views hold onto a context and I don't know if it is the Activity context or Application context if inflated from the inflater.
Since using the Application for view inflation does not seem to work well, you should be inflating from the Activity. And, hence, the views will hold a reference to the Activity that inflated them.

#schwiz é have implemented something similar.
I use the setRetainInstance in the fragment. on the fragment layout I have a FrameLayout placeholder where I put my webView inside.
This webView is created programmatically on the Fragment's onCreate using the Application Context, and I do a addView(webView) on the inflated layout in onCreateView().
webView = new WebView(getActivity().getApplicationContext());
And on onDestroyView I simply remove the webView from my framelayout placeholder.
It work really well, unless you try to play a video in fullscreen. That doesn't work because it expects an Activity Context

I am trying to save my View states in my fragment
In order to save and retain view's state you can just use View.onSaveInstanceState () and View.onRestoreInstanceState (Parcelable state)
It will help you to handle saving and restoring view state independantly from neither activity or fragment.
See this answer for more information about it (how to prevent custom views from losing state across screen orientation changes)

Related

Confusion regarding inflater.inflate Android documentation

I'm studying fragments from this link : http://developer.android.com/guide/components/fragments.html
There's a piece of code given as:
public static class ExampleFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
}
I was confused about the attachToRoot parameter so I looked up on Stack Overflow for some help and found good answers on similar problem. So what I understand is if you set it to true, the fragment gets attached to the activity's root layout, and derives its layoutparams from there. If it's false it would simply return the root of the inflated layout and acts like a standalone view for a fragment (deriving the layout params from the passed in container).
Now I read further down in the documentation regarding attachToRoot for the above example:
A boolean indicating whether the inflated layout should be attached to
the ViewGroup (the second parameter) during inflation. (In this case,
this is false because the system is already inserting the inflated
layout into the container—passing true would create a redundant view
group in the final layout.)
I don't get the last parenthesis statement where it says that it should be false because we're already inserting the layout into the container. What does it mean that we're already inserting into the container without attachToRoot as true? If the parameter is true, how would the final layout have redundant view groups. An example to elaborate this part would be a great help. Thanks.
I don't usually answer my own questions, but after doing a bit more research for this, I thought that maybe this will help someone else. Although Marcin's answer is correct, but I'm just answering a bit more in detail.
As per code :
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.example_fragment, container, false);
}
The second parameter container is a framelayout with id fragment_container that activity uses to add fragment to it's layout.
Now, if we dive deeper into inflate method of LayoutInflater class, this is the code (I'm just highlighting the relevant parts of code rather than the whole part) :
// The view that would be returned from this method.
View result = root;
// Temp is the root view that was found in the xml.
final View temp = createViewFromTag(root, name, attrs, false);
Firstly, it creates a temp view from the supplied root.
In case attachToRoot is true, it does this :
if (root != null && attachToRoot) {
root.addView(temp, params);
}
It adds the temp view created above to the root view (i.e. container).
In case attachToRoot is false, it does this:
if (root == null || !attachToRoot) {
result = temp;
}
As quite evident, in case attachToRoot is true, it simply returns the root (fragment_container i.e the id activity uses to place the fragment inside it.) after adding the temp view to it (Root view in example_fragment in this case)).
In case attachToRoot is false, it simply returns the root of the fragment's xml, i.e. the container parameter is just used to get layoutParams for fragment's root view (since it doesn't have a root, so it needs params from somewhere).
The problem in case of true, arises in above example, because the return value is root (fragment_container with added view temp, and fragment_container by default has a parent already.). Now, if you try to do a fragment transaction, you're trying to add a child view fragment_container(which already has a parent to another xml (framelayout that you defined to add the fragment to).
Due to this, Android throws the following exception:
if (child.getParent() != null) {
throw new IllegalStateException("The specified child already has a parent. " +
"You must call removeView() on the child's parent first.");
}
Problem while setting it to true and returning is that the view returned already has a parent, so can't be used else where. Other way, you could make a separate view group inside onCreateView (maybe a LinearLayout), set the parameter to true, and return the view. Then it'll work fine because the viewgroup won't have an existing parent.
This is my understanding of the above problem, I might be wrong in which case I'd like any Android expert to correct this.
It means that in case on onCreateView() returned View will be attached to container view anyway, so setting it to true in your onCreateView() would cause it to be added twice to container layout, which isn't what you usually want. And setting root view to not null and still have attachToRoot false allows inflated view to derive from root, w/o being added.
check this
View v =getLayoutInflater.inflate(R.layout.example_fragment, viewgroup);
and also this
View v =getLayoutInflater.inflate(R.layout.example_fragment, null);
now if you call this for the first scenario
Log.v("testing",String.valueOf(v.getParent() == null));
you will get false as the output, but when you check that on the second line you will get true.
Simple understanding is if you specify a parent(ViewGroup) it is attached to it, but if you add the last parameter the you are telling the inflater to skip the attachment of the View so your returned View has no parent, but if you specify null, who's going to be attached to? NOBODY so the returned View has no parent, and also like you stated it derives layoutParameters for the View.
If you dont add the last parameter the View will have its parent as the Activity View and hence will be returned in your OncreateView having already a parent, before it will be added, which will cause some Exceptions as a View cannot have different daddies and the Fragment will not be able to manage the View itself.
if add true return null from your oncreateView
The View that you return from onCreateView() method of a fragment, will be attached to the container of the fragment by the system. So the third parameter of inflate() method should be false in this case. If you set it to true then it will be attached to the root twice. So you should set it to false in this case.
Now, the inflated view derives its layout parameters from the second parameter of inflate() method not from the third boolean parameter.
ok from android official documentation Android documentation
What is Fragment?
A Fragment represents a behavior or a portion of user interface in an Activity. You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities).
How to Use Fragment in Activity?
A fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle. For example, when the activity is paused, so are all fragments in it, and when the activity is destroyed, so are all fragments. However, while an activity is running (it is in the resumed lifecycle state), you can manipulate each fragment independently, such as add or remove them.
What is Fragment Transaction
you can manipulate each fragment independently, such as add or remove them. When you perform such a fragment transaction, you can also add it to a back stack that's managed by the activity—each back stack entry in the activity is a record of the fragment transaction that occurred. The back stack allows the user to reverse a fragment transaction (navigate backwards), by pressing the Back button.
How Fragment and Activity Interact with each other
When you add a fragment as a part of your activity layout, it lives in a ViewGroup inside the activity's view hierarchy and the fragment defines its own view layout. You can insert a fragment into your activity layout by declaring the fragment in the activity's layout file, as a element, or from your application code by adding it to an existing ViewGroup.
Fragment Lifecycle methods onCreateView()
onCreate()
onPause()
And your question is
What does it mean that we're already inserting into the container without attachToRoot as true? If the parameter is true, how would the final layout have redundant view groups
and here is answar
suppose you have added already in your activity layout then you do not' need to make last parameter to true.because each time you perform add or remove of fragment their is a fragment transaction.
However if you have not added already a fragment in your layout then you can set it to true.
read this in doc
You can insert a fragment into your activity layout by declaring the fragment in the activity's layout file, as a element, or from your application code by adding it to an existing ViewGroup. However, a fragment is not required to be a part of the activity layout; you may also use a fragment without its own UI as an invisible worker for the activity.
all fragment transaction are added on activity back stack

Fragment vs. Custom View in Android

The Fragment and Custom View can achieve the similar function, I know that fragment is more re-usable comparing with custom view, any other benefits/enhancements for using Fragment? Is fragment supposed to replace Custom View, or just a enhancement for some specific purpose?
For instance, the code below is fragment:
public class TestFragment extends Fragment {
private TextView tv_name;
private Button btn_play;
private Button btn_delete;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.testfragment, container, false);
}
#Override
public void onStart() {
super.onStart();
tv_name = (TextView)getView().findViewById(R.id.tv_name);
btn_play = (Button)getView().findViewById(R.id.btn_play);
btn_delete = (Button)getView().findViewById(R.id.btn_delete);
}
}
The code for custom view:
public class TestCustomView extends LinearLayout {
private TextView tv_name;
private Button btn_play;
private Button btn_delete;
public TestCustomView(Context context, AttributeSet attrs){
super(context, attrs);
setOrientation(LinearLayout.HORIZONTAL);
setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
tv_name = new TextView(context);
addView(tv_name);
btn_play = new Button(context);
addView(btn_play);
btn_delete = new Button(context);
addView(btn_delete);
}
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.testfragment, container, false);
}
}
Both TestFragment and TestCustomView can create a view consisting of TextView and Buttons, and use tags of Framelayout/fragment and com.packagename.TestCustomView to declare in the activity's xml layout file, but what's the advantages to use Fragment?
Fragment can be used in different scenarios but most used are:
wrapper around a view
headless fragment - i.e. no view => not very helpful in general but can be used
retainable fragment - can be any of above. By using Fragment.setRetainInstance(true) you can bypass Fragment.onDestroy(), i.e. can keep fragment data on configuration changes but fragment view structure is still destroyed/recreated
can be added to activity back stack, i.e. easy Back button previous state restore
There are cases where fragment are complete pain in the neck, then there are cases where they can achieve results quicker.
For some custom and more flexible situations fragments can get cluttered and managing them would be difficult. So dealing with views directly can be really handy and more helpful for some cases. But everything is based on requirements.
Note View has its own life cycle too and can store/recreate saved instance state. A little bit more work but it has the option too.
Custom Views have the advantage of simplicity and their primary purpose is to display a piece of data on the screen. They must rely on other components in order to do more.
Think of Fragments as a functional unit, a way to display a portion of UI that has a specific purpose, using one or more Views. Fragments are connected to the Activity lifecycle and they can include and control Loaders to populate the Views with data. They can also include sub-fragments. Finally, they can also be added to a synthetic back stack. They can do many things and are somewhat complex to learn.
As you can see, Fragments have much more in common with Activities than they have with custom views.
As a side note, Fragments can also be headless (with no UI). Headless fragments provide a way to encapsulate non-visual functionality relying on the Activity lifecycle in a separate component.
Fragments come with their own lifecycle, which can be a hinderance or a bonus, depending on what you need.
Fragments get lifecycle methods like onResume or onSavedInstanceState, which can help you deal with state transitions in your application. If you're using custom views, you need to handle that kind of things on your own.
There are people who advocate against using fragments, I suggest reading https://developer.squareup.com/blog/advocating-against-android-fragments/
The most useful functionality of using Fragments over Custom Views is that they have their own Lifecycle Callbacks, i.e. we can register our own FragmentLifecycleCallbacks to do some operations before/after Fragment creation/destruction.
We can create our own FragmentLifecycleCallbacks and register it with Activity to inject dependencies in Fragment through Dagger.
There are some workarounds to inject dependencies in Custom Views too, but doing it through FragmentLifecycleCallbacks is much cleaner and easier to do.

Android Development, how does this line of code become produced?

i'm reading this android dev book and im stuck understanding how this line of code is error free (please keep in mind i've gotten rid of some code because for more focus on this part.
public View onCreateView(LayoutInflater layoutToInflate, ViewGroup parent, Bundle saveState)
{
View v = layoutToInflate.inflate(R.layout.activity_main_fragment,parent,false);
return v;
}
from what i believe, i need a method that returns a view because the Class extends from a Fragment class, not an activity so i have to explicitly find the view, the parameters are straight forward what i dont understand is how we create a view and set it equal to layoutToInflate...false;
I think you have a misunderstanding what the concepts of Fragments are. They reside inside an Activity. If a Fragment has a UI, it needs the parent Activity to also have a UI. That also means Fragments have a ViewParentbelonging to an Activity. This parent is given to the Fragment by the ViewGroup parent argument. So when creating a Fragment with a UI, you need to inflate the layout belonging to your Fragment and pass it to the Activity, which adds it to the ViewGroup parent. So that's why you get a LayoutInflater to inflate your Fragment's view:
View v = layoutToInflate.inflate(R.layout.activity_main_fragment,parent,false);
Afterwards you return it to give it to the parent Activity.
layoutToInflate is a variable of LayoutInflater and R.layout.activity_main_fragment is the name of the layout file to be inflated.

More instances of fragment at one time - resources referencing to wrong views in android

this is a though one for me. I have a MainActiviy which extends FragmentAnctivity. There I have 1 FrameLayout and buttons below to change frame's content. I do so by switching show/hide for created fragments which I added to FrameLayout before in OnCreate.
I'm also nesting more fragments in 1 fragment (As I have 1 fragment for 1 type of content and inside of it there is listFragment which is changed to DetailFragment after OnItemClick... again with show/hide approach).
Problem is that in 2 different contents I have 2 different instances of 1 Fragment class, so those 2 instances use 1 same layout file. And although the first of those fragment is hidden and 2nd is shown, when I change some view through 2nd instance then layout of 1st instance is changed and 2nd remains same as before. (Hope it is understandable)
I guess it's totally a mistake in managing and understanding of fragments' lifecycle, so can please someone help me to solve this?
Thanks very much :)
I suppose you get main point of fragments using practices. Your problem is simple. I almost sure you use getActivity().findViewById(...) calls to access views in your Fragment (or nested Fragment whatever). I this case Activity would return you fist view with defined id from whole your views hierarchy.
Solution is pretty simple - you just must avoid getActivity().findViewById(...) construction and get all links to views in onCreateView() callback and use exact this link with all future operations. Than everything will be ok. Here is simple example:
private TextView mDummyText;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_name, container, false);
initMembersViews(v);
return v;
}
private void initMembersViews(View v) {
mDummyText = (TextView) v.findViewById(R.id.fr_houses_list_text);
}
Hope it would helps you! Good luck!

Android views and custom orientation change, what's the best way to handle?

I've an android app that has multiple views. I've disabled the automatic orientation switching by declaring android:configChanges="keyboardHidden|orientation|screenSize" for my activity.
Q1) How I should re-activate views in onConfigurationChanged()? It seems I need to reset the content view using setContentView method. Is there an alternative way to signal view that now you should inflate the landscape/portrait version of the layout? Some of my views contain quite complicated states/modeless dialogs etc that can appear any time so deleting the view object and re-instantiating it just doesn't sound right.
Q2) What's the best way in onConfigurationChanged() to know which view we should actually now activate i.e. what was focused when orientation change was initiated? I'm reluctant to rely on getCurrentFocus() as it can be null sometimes.
Q1) How I should re-activate views in onConfigurationChanged()?
As you mentioned, setContentView() is a good way to do this. Just remember to not always pass in a layout XML to this method. Instead, pre-inflate your layout into a View object and pass in that View object to setContentView. That way, you don't incur the cost of re-creating your view object on every orientation change.
The following is a sample code - may not work as-is; but meant to illustrate my point.
View mLandscapeView;
View myPortraitView
protected void onCreate(Bundle bundle){
View myLandscapeView = getLayoutInflater().inflate(R.layout.my_landscape, null);
View myPortraitView = getLayoutInflater().inflate(R.layout.my_portrait, null);
//...
}
#Override
protected void onConfigurationChanged(Configuration config){
if(config.orientation = Configuration.ORIENTATION_LANDSCAPE){
//adjust mLandscapeView as needed
setContentView(mLandscapeView);
}
// And so on ...
}

Categories

Resources