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.
Related
I'm using databinding to make handling views and binding data to them easy for myself and i also use navigation component. Evething works fine but i can not save fragments'(destination) UI state. For example if i have a recyclerview and i scroll to last position and i navigate to another fragment and when i get back to previous fragment, i can see first recyclerview's position. Now, it's either databinding scrolled recyclerview to first position or recyclerview is recreated. First i thought it's because of navigation component that does not save destinatins views state so i read with more attention and i reached navigate to a destination which in popUpToSaveState and restoreSaveState section says:
When you use app:popUpTo to navigate to a destination, Navigation 2.4.0-alpha01 and higher allow you to optionally save the states of all destinations popped off of the back stack. To enable this option, include app:popUpToSaveState="true" in the associated element:
so in all my actions i added app:popUpToSaveState="true" but did not solve my issue. So i kept reading and i faced this title about support multiple back stack. In section of restoring state it says:
Navigation XML
In Navigation XML, elements in your navigation graph can use the app:popUpToSaveState attribute to save the state of any destinations that the action popped using app:popUpTo. They can also use the app:restoreState attribute to restore any previously saved state for the destination defined in the app:destination attribute.
so i also add app:restoreState="true" to all my actionss but did not saved destinations(fragments) views state. so i realized it's not the navigation component that causes this problem it might be databinding that does not save fragments state. So i read more about databinding. Nothing attract my attention except this title Bind layout views to Architecture Components and i saw this sample code:
class ViewModelActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
// Inflate view and obtain an instance of the binding class.
UserBinding binding = DataBindingUtil.setContentView(this, R.layout.user);
// Specify the current activity as the lifecycle owner.
binding.setLifecycleOwner(this);
}
}
so i thought i did not Specify fragments as the lifecycle owners and i added binding.setLifecycleOwner(LIFE_CYCLER_OWNER); to all fragment that i used databinding.
Here is how use databinding in fragment :
#Override
public View onCreateView(#NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
fragBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_season_lists, container, false);
fragBinding.setLifecycleOwner(this);
return fragBinding.getRoot();
}
and here is how i use databinding in recyclerview:
private final LayoutInflater inflater;
public SectionDataRecyclerView(
Context context,
ArrayList<SingleModel> sectionList) {
this.inflater = LayoutInflater.from(context);
this.sectionList = sectionList;
}
#NonNull
#Override
public ViewHolder onCreateViewHolder(#NonNull final ViewGroup parent, int viewType) {
final ActivitySingleCourseListBinding binding = DataBindingUtil.inflate(inflater, R.layout.activity_single_course_list, parent, false, null);
binding.setLifecycleOwner(binding.getLifecycleOwner());
return new SectionDataRecyclerView.ViewHolder(binding);
}
I used both this; And getViewLifecycleOwner() for binding.setLifecycleOwner(LIFE_CYCLE_OWNER); in fragments but non of them saved views state like recyclerview position. To make sure it is the databinding that does not save states i created and test fragment with a recyclerview it and RecyclerView.Adapter in the exsisting navigation component without using databinding. it turned out that it's because of databinding that fragments are (let's say) recreating views so they can not have their state saved. It got too long so i ask my question in another way:
Question :
In case if using both navigation component and databinding how can i navigate through frtagments and have fragments state and all views state the way they were when i navigate back?
As a preamble; I have an Android app containing 4 activities, and I wish to transfer these into a single activity containing Fragments (two extending ListActivity, and two extending Activity), with navigation between Fragments enabled by a Navigation Drawer . I have attempted to turn these Activities into ones extending ListFragment and Fragment, however much of the code within the activity ceases to function: for example, getSystemService(NOTIFICATION_SERVICE), the unbindService and the 'registerReciever', and primarily the different onCreateOptionsMenus contained within the disparate activities.
Therefore I ask, would it be possible to port any separate Activities to a single fragmented one, but still retaining the same function as with the separate focused activities, with minimal editing?
Also, regarding the transition process would one need to end a previous Fragment within the main Activity to display another Fragment in the same space?
Yes it is possible, fragments have a very similar lifecycle to activities (See here). You'll find it is probably a case of copy/paste from your activity classes to your fragment classes with very minor tweaks to have them work as they were as separate activities. The only legwork you will have to do is swapping these fragments from your activities.
For example, an activity with its onCreate function that looks like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
// Find views
homeMessagesButton = (Button) v.findViewById(R.id.homeMessagesButton);
homeCreateMatchButton = (Button) v.findViewById(R.id.homeCreateMatchButton);
homeMyMatchesButton = (Button) v.findViewById(R.id.homeMyMatchesButton);
homeMyTeamsButton = (Button) v.findViewById(R.id.homeMyTeamsButton);
homeSquadButton = (Button) v.findViewById(R.id.homeSquadButton);
homeSettingsButton = (Button) v.findViewById(R.id.homeSettingsButton);
// Set click listeners
homeMessagesButton.setOnClickListener(this);
homeCreateMatchButton.setOnClickListener(this);
homeMyMatchesButton.setOnClickListener(this);
homeMyTeamsButton.setOnClickListener(this);
homeSquadButton.setOnClickListener(this);
homeSettingsButton.setOnClickListener(this);
}
Will now look like this in a fragment (note the fragment uses the onCreateView lifecycle method to inflate its view inside the activity the fragment will appear in)
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View v = inflater.inflate(R.layout.fragment_home, container, false);
// Find views
homeMessagesButton = (Button) v.findViewById(R.id.homeMessagesButton);
homeCreateMatchButton = (Button) v.findViewById(R.id.homeCreateMatchButton);
homeMyMatchesButton = (Button) v.findViewById(R.id.homeMyMatchesButton);
homeMyTeamsButton = (Button) v.findViewById(R.id.homeMyTeamsButton);
homeSquadButton = (Button) v.findViewById(R.id.homeSquadButton);
homeSettingsButton = (Button) v.findViewById(R.id.homeSettingsButton);
// Set click listeners
homeMessagesButton.setOnClickListener(this);
homeCreateMatchButton.setOnClickListener(this);
homeMyMatchesButton.setOnClickListener(this);
homeMyTeamsButton.setOnClickListener(this);
homeSquadButton.setOnClickListener(this);
homeSettingsButton.setOnClickListener(this);
return v;
}
Changing fragments within the activity is done through the use of the fragmentTransaction manager as shown in the android training site here
Since your service requires to be bound to a activity, rather than a fragment. This becomes trivial as fragments can retrieve the activity they are bound to. For example, the code below used in a fragment will automatically get the activity they exist within.
getActivity().getSystemService(Context.LOCATION_SERVICE);
If you want to use getSystemService(NOTIFICATION_SERVICE) in your fragments you need to use the context of your activity. Context.getSystemService(NOTIFICATION_SERVICE). Some methods work on Activities and if you want to use them elsewhere you need to get the context.
Moving your code from Activity is fairly simple. For example onCreateOptionsMenu is also available in Fragments and as for your methods that require a context or Activity, you can call getActivity() from your Fragment to get a reference to the parent Activity.
EDIT : to replace a Fragment, there's also a method for that from the FragmentTransaction class, taking the id of the container View in argument.
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!
Normally I would use a separate activity for each "screen" I wish to display, using different XML files.
However I'm working with Dynamically loading jar files into an android application, so therefore at runtime, I am not aware of how many activities there will be, or how many screens there will be.
At the moment, using java reflection, I am able to return a list of strings from the dynamically loaded java file, and draw each list item, as a separate button onto the screen. If one of these buttons is clicked, i want to be able to load a different "screen" on the stack. So when I press back from this new screen, it goes to the previous screen that called it.
Is it possible to do this without creating a new activity and passing a new intent to it and of course making relevant changes to the android manifest file?
To use blackberry functionality as an example - Is there an equivalent in android to blackberry's push and pop screens? Where the screen ur pushing/popping, would simply extent MainScreen?
If anyone has questions, or If I've been vague, please comment and I will try my best to explain myself, any help is very much appreciated.
The Android equivalent to BB's push/pop screen is startActivity()/finish(). However, you can manage your own views in a single activity by either using a container view (such as ViewSwitcher, as #hasanghaforian suggests) or by simply calling setContentView() with a new view hierarchy whenever you want to change the screen. Be aware that when you call setContentView, any view references that you obtained by calling findViewById will be stale.
In my opinion you should use Fragment. I assume that you have some piece of code where you iterate over the strings:
for(String def : definitions) {
Fragment f = new CustomFragment();
Bundle b = new Bundle();
b.putString("STRING_DEF",def);
f.setArguments(b);
fragments.add(f);
}
in above piece of code a collection of Framents is just created. Let's look at the CustomFragment implementation:
CustomFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String def = getArguments.getString("STRING_DEF");
//write code to create view
return view;
}
}
Now in your ListActivity you have to implement on click listener more or less like this like this
public void onListItemClick(ListView l, View v, int position, long id) {
FragmentManager fragMgr = getFragmentManager();
FragmentTransaction t = fragMgr.beginTransaction();
t.replace(R.id.id_of_view_place_holder_for_fragment,
fragments.get(position),"FRAGMENT_TAG");
t.commit();
}
you can use ViewSwitcher. ViewSwitcher is a ViewAnimator that switches between two views, and has a factory from which these views are created. You can either use the factory to create the views, or add them yourself. A ViewSwitcher can only have two child views, of which only one is shown at a time.Or you can use fragments.
If you refer to if it is possible to have different layouts in the same activity, the answer is yes.
Activities are independent of layouts, you don't assign the layout for an activity in the manifest, you define what layout to use in the activity calling setContentView() method from Activity class to set a layout.
So if you want to have some layouts (screens) the only thing you have to do is define various layouts and use them when you want calling setContentView(R.layout.the layout), after this call, the layout chosen will be displayed.
If you can't create the layout statically by xml, you can create it dinamically by code as needed by demand each time you want.
In addition you can have a stack of layouts, each time you need a new screen, build it, push it to the stack and call setContentView() method, when you don't need it more, pop off the stack and call setContentView() with the new layout in the top of the stack.
Hope it help you
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)