Playing around with Fragments, I came to wonder about the right way to populate my fragments.
As the doc says, I use the newInstance() pattern to add arguments to my fragments :
public static ItemFragment newInstance(ItemRealm item, AnnaleModel model) {
ItemFragment fragment = new ItemFragment();
Bundle args = new Bundle();
args.putString(MyPagerAdapter.KEY_ITEM, item.getId());
args.putParcelable(MyPagerAdapter.KEY_MODEL, model);
fragment.setArguments(args);
return fragment;
}
But then, there is several behaviours I can see happening on the net.
The most seen it to put getArguments() in the onCreateView() method and put the results in fields :
protected String itemId;
protected Model mModel ;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle args = getArguments();
if (args == null || (!args.containsKey(MyPagerAdapter.KEY_ITEM) || !args.containsKey(MyPagerAdapter.KEY_MODEL))) {
Log.e("TAG", "incorrect Bundle");
return null;
}
itemId = args.getString(MyPagerAdapter.KEY_ITEM);
mModel = args.getParcelable(MyPagerAdapter.KEY_MODEL);
}
I can see on some other places to put the same exact thing in the Fragment.onCreate() method instead of onCreateView().
And the last behaviour is to call getArguments() in the getter.
private Model getModel(){
if (getArguments() != null) {
return getArguments().getParcelable(AnnalePagerAdapter.KEY_MODEL);
}
Log.e("Dan", "ItemFragment :: getModel (279): model==null !!");
return null;
}//I can also think about some lazyloading is needed
The questions are then :
Is there now a consensus on which pattern to use (these 3 or even another) ?
Is there some contexts I should rather use one than another ?
Don't use a getter. That just complicates things imo. Why include fragment logic in the the Model class, right?
As for onCreateView() vs onCreate(), I call getArguments() in onCreate() because this method is called before the view is created. So, if you have any calculations or other stuff then you can do it here. Save it in global vars and set them in onCreateView. But instead of doing that, you can just do it in onCreateView, right? So what's the difference then? It helps to keep the logic separated. Initialize/update the view in onCreateView and do pre-calculations stuff in onCreate
Example from google's samples
Related
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().
I have five fragments a user can switch between. One of these fragments loads a list of users from the server to populate the UI list on the fragment. I need the list information to persist if a user swipes to a different fragment and then swipes back to the original. I do not want the fragment to reload the users every time a user leaves the fragment and goes back.
I am looking at setRetainInsance(true) and was wondering if this is possible solution? What would be the best way for the fragment to retain the information without being created from scratch each time.
I am using this to switch between fragements -getSupportFragmentManager().beginTransaction().replace(R.id.searchLayout, ratingFragment).commit();
A Fragment is Just like any other object.
on Fragment transaction , the Fragment does not call OnCreate() method instead it starts from onCreateView , therefore , load your users and save it an instance variable and assign it in onCreate()
Example
class MyFragment extends Fragment{
List<users> userList;
void onCreate(){
userList = getUserList();}
//the list is loaded during Oncreate();
now imagine you have replaced the Fragment
now According to Andorid Framework , onCreate() is not Called again
instead onCreateView() is called
void onCreateView(){
//you can check whether instance Variable is initialised or not
if(userList != null) {
listview.setAdapter(new Myadapter(this,userList);
Replace the fragment by adding it to backstack.
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.addToBackStack(tag);
fragmentTransaction.replace(container, fragment, tag);
fragmentTransaction.commit();
Also create object of View and return it if it's not null.
private void View view ;
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
if (view != null)
return view;
view = inflater.inflate(R.layout.fragment_browse_recipe, container, false);
//initialize layout views
return view;
}
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.
I was just wondering how I could send parameters or arguments to a Fragment before it is created. Because I want to pass an array of strings to the fragment so that it could put all of them in the layout when it is created. For example I am making a Leaderboard fragment, and my activitiy would pass in all of the scores etc. that the fragment would use to display. I understand that I can use the Bundle and the .setArgs but will that work for my case?
Thank you
** EDIT **
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View singleplayerView = inflater.inflate(R.layout.singleplayer_tab, container, false);
String[] scores = (String[]) getArguments().get("scores");
TextView tview = (TextView) singleplayerView.findViewById(R.id.player_name0);
tview.setText(scores[0]);
setupRank(singleplayerView);
return singleplayerView;
}
public static SingleplayerTab newInstance(String[] scores) {
SingleplayerTab spt = new SingleplayerTab();
Bundle args = new Bundle();
args.putStringArray("scores", scores);
spt.setArguments(args);
return spt;
}
CODE THAT CALLS IT
String[] scores = {"hello"};
Fragment singlePlayerFragment = SingleplayerTab.newInstance(scores);
I understand that I can use the Bundle and the .setArgs but will that work for my case?
A Bundle can hold an String[] or an ArrayList<String>.
Moreover, this is the way you should do it, rather than a custom constructor. Android automatically recreates your fragments on a configuration change (e.g., screen rotation), and it will use your public zero-argument constructor for that. Hence, unless you use the arguments Bundle, or something else, you will lose your string array on a configuration change.
The recommended approach for this is to use a factory method, such as this one from an EditorFragment:
static EditorFragment newInstance(int position) {
EditorFragment frag=new EditorFragment();
Bundle args=new Bundle();
args.putInt(KEY_POSITION, position);
frag.setArguments(args);
return(frag);
}
In this case, I want to pass int position into the fragment. I isolate packaging this into the Bundle into the factory method (newInstance()). When I need to create an instance of this fragment, I call EditorFragment.newInstance() instead of new EditorFragment, so I can supply the position. My fragment can get the position by reading the KEY_POSITION value out of the getArguments() Bundle. I use this approach in (among other places) this sample project, showing loading 10 of these editors into a ViewPager.
I'm starting an Activity through the usual means:
Intent startIntent = new Intent(this, DualPaneActivity.class);
startIntent.putExtras(((SearchPageFragment) currentFragment).getPageState());
startActivity(startIntent);
When this activity loads, it places a Fragment in a frame like so:
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
Fragment currentFragment = fragment;
currentFragment.setArguments(getIntent().getExtras());
transaction.replace(R.id.singlePane, currentFragment);
transaction.commit();
Seems simple, right?
However, you can inside of onCreateView() method access three separate bundles (four, if you include the one included in the Fragment's onCreate(Bundle savedInstanceState)):
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Fill state information
Bundle bundle;
if(savedInstanceState != null) bundle = savedInstanceState; // 1
else if(getArguments() != null) bundle = getArguments(); // 2
else bundle = getActivity().getIntent().getExtras(); // 3
setPageState(bundle);
}
In the above instance, I've worked out from trial and error that the bundle I want is the second one, the one retrieved from getArguments().
From my understanding the third one from getActivity().getIntent().getExtras(); is actually calling the Bundle from the intent used to start containing activity. I also know from experimentation that savedInstanceState seems to always be null. But where is it coming from and why is it null?
The documentation says this:
savedInstanceState If non-null, this fragment is being re-constructed
from a previous saved state as given here.
That doesn't help me - It's bugging me more than stopping me from moving on. Can someone help me out with this annoyance?
To the best of my knowledge, onCreateView and onCreate() are both passed the bundle from onSaveInstanceState().
So if you override onSaveInstanceState() and put data in the bundle, you should be able to retrieve it in onCreateView(). That is why the documentation says that the bundle will be non-null when the fragment is re-constructed from a previous saved state.