I have been putting some debugging into my app to try to work out what is happening with the lifecycle.
I have some tabs and each tab content is a different fragment, each time a tab is changed onCreateView is called in the corresponding fragment.
In some of my onCreateViews I am currently mocking up some data and injecting table rows etc and then inflating the view every time. Like in the example below:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Log.d(LOG_TAG, "******************* onCreateView() is being called in the Container Fragment *********************");
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.fragment_patient, container, false);
mTabHost = (FragmentTabHost)rootView.findViewById(android.R.id.tabhost);
mTabHost.setup(getActivity(), getChildFragmentManager(), android.R.id.tabcontent);
//Set up tabs
mTabHost.addTab(mTabHost.newTabSpec("home_addresses").setIndicator("Home Addresses"), HomeAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("postal_addresses").setIndicator("Postal Addresses"), PostalAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("temp_addresses").setIndicator("Temporary Addresses"), TemporaryAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("email_other").setIndicator("Email / Other"), TemporaryAddressesFragment.class, null);
mTabHost.addTab(mTabHost.newTabSpec("tel_fax").setIndicator("Telephone & Fax"), TemporaryAddressesFragment.class, null);
//Set to home addresses tab
mTabHost.setCurrentTab(0);
tempAddressBtn = (Button)rootView.findViewById(R.id.temp_addr_tab_btn);
tempAddressBtn.setTransformationMethod(null);
tempAddressBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
postalAddressBtn = (Button)rootView.findViewById(R.id.postal_addr_tab_btn);
postalAddressBtn.setTransformationMethod(null);
postalAddressBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
homeAddressBtn = (Button)rootView.findViewById(R.id.home_addr_tab_btn);
homeAddressBtn.setTransformationMethod(null);
homeAddressBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
telFaxBtn = (Button)rootView.findViewById(R.id.tel_fax_tab_btn);
telFaxBtn.setTransformationMethod(null);
telFaxBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
emailOtherBtn = (Button)rootView.findViewById(R.id.email_other_tab_btn);
emailOtherBtn.setTransformationMethod(null);
emailOtherBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
tabHandler(v);
}
});
return rootView;
}
My question is should I be doing this each time the onCreateView method is called? Is the view already cached somewhere?
I am seeing code in tutorials that looks a bit like:
#Override
protected void onCreate(Bundle savedInstance) {
super.onCreate(savedInstance);
setContentView(R.layout.pager_activity);
if (savedInstance == null) {
PagerFragment frag = PagerFragment.newInstance(buildPagerData());
FragmentManager fm = getSupportFragmentManager();
fm.beginTransaction().add(R.id.layout_fragments, frag, PAGER_TAG).commit();
}
findViewById(R.id.btnFragments).setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
changeFragmentVisibility();
}
});
}
I know this Fragment in the example above extends from FragmentActivity and mine just extend from Fragment but it is using the fragment manager and checking if the state is null before deciding to instantiate a new fragment.
Edit: I am guessing as well that as this happens in onCreate and not onCreateView it happens less frequently?
I am wondering if this is something that I should be doing or if its ok to continue with the way I am going?
In my opinion you should manage your tabs from the parent Activity. Each Fragment should be responsible for one part of the UI.
Another advantage of that approach is that each Fragment layout will be a bit easier. And it will be easier to apply different containers if needed (e.g ViewPager).
Related
I have setup a very simple test project https://github.com/ArtworkAD/ViewPagerDialogTest to evaluate following situation: the main activity has a view pager which hosts a single fragment using support fragment manager:
public class MainActivity extends AppCompatActivity {
// ...
#Override
protected void onCreate(Bundle savedInstanceState) {
// ...
viewPager.setAdapter(new ViewPagerAdapter(getSupportFragmentManager()));
// ...
tabLayout.setupWithViewPager(viewPager);
}
#Override
protected void onResume() {
super.onResume();
MainActivity.CustomDialog dialog = (MainActivity.CustomDialog) getSupportFragmentManager().findFragmentByTag(MainActivity.CustomDialog.TAG);
if (dialog == null) {
new MainActivity.CustomDialog().show(getSupportFragmentManager().beginTransaction(), MainActivity.CustomDialog.TAG);
}
}
// ...
}
When the activity is resumed a dialog fragment is shown inside the main activity.
The single fragment inside the view pager is defined like this:
public class RootFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.root_fragment, container, false);
if (savedInstanceState == null) {
getFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
}
return root;
}
}
This root fragment allows us to stack other fragments on the "root_frame". So we stack another and another:
public class FirstLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
View root = inflater.inflate(R.layout.first_level_fragment, container, false);
root.findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
SecondLevelFragment f = (SecondLevelFragment) getActivity().getSupportFragmentManager().findFragmentByTag("NESTED");
if (f == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
}
}
});
return root;
}
public static class SecondLevelFragment extends Fragment {
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
setRetainInstance(true);
return inflater.inflate(R.layout.second_level_fragment, container, false);
}
}
}
This works great! The stacking idea is taken from https://stackoverflow.com/a/21453571/401025 . However when dialog is shown and the users goes to the second level fragment and rotates the screen I get following exception:
E/AndroidRuntime: java.lang.RuntimeException: Unable to start activity
ComponentInfo{de.azzoft.viewpagerdialogtest/de.azzoft.viewpagerdialogtest.MainActivity}:
java.lang.IllegalArgumentException: No view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
E/AndroidRuntime: Caused by: java.lang.IllegalArgumentException: No
view found for id 0x7f0c0083
(de.azzoft.viewpagerdialogtest:id/root_frame) for fragment
SecondLevelFragment{15c0db38 #0 id=0x7f0c0083 NESTED}
Full stack trace: https://github.com/ArtworkAD/ViewPagerDialogTest/blob/master/README.md
Without the dialog appearing everything works great. You can test it by downloading the test project.
It seems that the dialog, which is actually a fragment, messes up fragment hierarchy when it is added to the activity. Any ideas how to fix this?
It is important that the second fragment is retained.
No view found for id 0x7f0c0083 (de.azzoft.viewpagerdialogtest:id/root_frame) for fragment SecondLevelFragment
When Activity recreates on rotate, the Activity FragmentManger tries to add the SecondLevelFragment into R.id.root_frame . But the root_frame view is not in Activity layout, its in FirstLevelFragment layout. Thats why the app crashes.
You have to make two changes to fix this issue.
Add the FirstLevelFragment into the RootFragment using the getChildFragmentManager
getChildFragmentManager().beginTransaction().add(R.id.root_frame, new FirstLevelFragment(), "ROOT").commit();
Add the SecondLevelFragment using FragmentManager
getFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
Finally remove the setRetainInstance from FirstLevelFragment and SecondLevelFragment as nested fragments doesn't required to set retain.
If you need to pop back the SecondLevelFragment on back press you need to pass the back press the event to RootFragment and pop from back stack.
Override the back press on activity
#Override
public void onBackPressed() {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.viewpager);
if(fragment instanceof RootFragment){
boolean handled = ((RootFragment)fragment).onBackPressed();
if(handled){
return;
}
}
super.onBackPressed();
}
And handle the back press on RootFragment
public boolean onBackPressed() {
int count = getChildFragmentManager().getBackStackEntryCount();
if(count > 0){
getChildFragmentManager().popBackStackImmediate();
return true;
}
return false;
}
I created a Pull request to your repository . please check
https://github.com/ArtworkAD/ViewPagerDialogTest/pull/1
Let me know if any questions.
If you override onDismiss so resolved crash. enjoy it.
#Override
protected void onResume() {
super.onResume();
DialogFragment dialog = (DialogFragment) getSupportFragmentManager().findFragmentByTag(TAG);
if(dialog == null){
CustomDialog.newInstance().show(getSupportFragmentManager(), TAG);
}
}
public static class CustomDialog extends DialogFragment {
public static CustomDialog newInstance() {
CustomDialog d = new CustomDialog();
return d;
}
#Override
public void onDismiss(DialogInterface dialog) {
// super.onDismiss(dialog);
Toast.makeText(getActivity(), "onDismiss", Toast.LENGTH_LONG).show();
}
#Override
public void onCancel(DialogInterface dialog) {
// super.onCancel(dialog);
Toast.makeText(getActivity(), "onCancel", Toast.LENGTH_LONG).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle("Dialog");
builder.setMessage("This is a message!");
builder.setPositiveButton("Okay", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
builder.setNegativeButton("No", new DialogInterface.OnClickListener() {
#Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(getActivity(), "onClick", Toast.LENGTH_LONG).show();
}
});
return builder.show();
}
}
If you want to keep the state of your Fragments you should use a FragmentStatePagerAdapter.
From the docs:
Implementation of PagerAdapter that uses a Fragment to manage each
page. This class also handles saving and restoring of fragment's
state.
If you use this you can also remove the setRetainInstance(true) calls.
Well, I had downloaded your Test app and it seems that I have fixed the problem.
In your FirstLevelFragment class, comment the following line
//if (nestedNestedFragment == null) {
getActivity().getSupportFragmentManager().beginTransaction().add(R.id.root_frame, new SecondLevelFragment(), "NESTED").addToBackStack(null).commit();
//}
And
Comment setRetainInstance(true); in SecondLevelFragment
I think you missed setContentView() in onCreate() of your Activity. See your Fragment can not be added without a View hierarchy. Your Fragment is hosted by an activity. So you need to set the content to the activity first.
Hope this Helps,
Thanks.
I'm trying to get back to a fragment from a fragmentActivity, but it is not working, I'm getting NullPointerException, When I hit cancel I need to update the previous fragment. How can I get back to a fragment and update it? don't know why this is happening.
public class AddEscolas extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addescola);
mSessao = new SecurePreferences(AddEscolas.this, "sessao");
pesquisa = (EditText) findViewById(R.id.campo_pesquisa);
nomeEscola = (TextView) findViewById(R.id.nomeEscola);
logradouro = (TextView) findViewById(R.id.endereco);
cidade = (TextView) findViewById(R.id.cidade);
procura = pesquisa.getText();
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(pesquisa.getWindowToken(), 0);
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
findViewById(R.id.btnProcurar).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
new Pesquisa().execute();
mFerramentas.hideKeyboard(AddEscolas.this, pesquisa);
}
});
findViewById(R.id.btnCancelar).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
}
});
}
}
FragmentClass :
public class EscolasFragment extends Fragment {
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_escolas, container, false);
ImageView add = (ImageView) rootView.findViewById(R.id.add);
mSessao = new SecurePreferences(getActivity(), "sessao");
add.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
Intent addIntent = new Intent(getActivity(), AddEscolas.class);
startActivity(addIntent);
}
});
return rootView;
}
}
Suppose you just need to call finish(), like this:
findViewById(R.id.btnCancelar).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
finish();
}
});
The reason for the NullPointerException maybe is that the layout id of R.id.content_frame could not be found in the AddEscolas activity.
If you need to update the EscolasFragment fragment after the AddEscolas activity exits, you may do as follow steps:
use startActivity() from the EscolarFragment fragment to call the AddEscolas activity.
use onActivityResult() in the activity containing the EscolarFragment fragment to extract result returned by the AddEscolas activity.
use onResume() in the EscolarFragment fragment to update the UI.
You have wrong idea about what FragmentActivity is. Activities host fragments. You cant go to fragment without having activity. Read the docs
Null pointer exception comes from
fragmentManager.beginTransaction().replace(R.id.content_frame, fragment).commit();
you need to give instance to 'fragment'. Somehing like:
Fragment fragment = new YourFragment();
Simply call onBackPressed(); in the FragmentActivity.
I currently have an Unit Converter app that I'm working in.
Here I've used multiple Blank Activities. Where each Unit's Activity can be opened using MainActivity. But now I want to make it tablet friendly.
Hence I want to use FragmentActivity now. Is it possible to convert the Blank Activities to Fragment Activities.?
All you need to do is take all View-specific logic from the Activity to a Fragment, then load the Fragment in your Activity.
For example,
public class MainActivity extends Activity {
#InjectView(R.id.button)
public Button button;
#OnClick(R.id.button)
public void onButtonClick(View view) {
Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show();
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
}
}
This type of logic goes in
public class MainFragment extends Fragment {
#InjectView(R.id.button)
public Button button;
#OnClick(R.id.button)
public void onButtonClick(View view) {
Toast.makeText(this, "Hello!", Toast.LENGTH_SHORT).show();
}
#Override
public void onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_main, container, false);
ButterKnife.inject(this, view);
return view;
}
}
And your Activity needs to display this fragment either statically, or dynamically. If you go dynamical, you'll need the following lines in your Activity:
public class MainActivity extends FragmentActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
FragmentManager fm = getSupportFragmentManager();
if(savedInstanceState == null) {
fm.beginTransaction()
.add(R.id.container, new MainFragment())
.commit();
}
fm.addOnBackStackChangedListener(new OnBackStackChangedListener() {
#Override
public void onBackStackChanged() {
if(getSupportFragmentManager().getBackStackEntryCount() == 0) finish();
}
});
}
}
If you go static, then you need to specify the fragments in your layout XML for the activity.
http://developer.android.com/guide/components/fragments.html#Adding
I would visit the Android website as they give a fairly good explanation on how fragments work.
You can learn how to add them to your existing application by another Android link here.
I was following tutorials on how to make Tabs using fragments.
each tab has this in the java files:
public class rdfri extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if (container == null) {
// We have different layouts, and in one of them this
// fragment's containing frame doesn't exist. The fragment
// may still be created from its saved state, but there is
// no reason to try to create its view hierarchy because it
// won't be displayed. Note this is not needed -- we could
// just run the code below, where we would create and return
// the view hierarchy; it would just never be used.
return null;
}
return (LinearLayout)inflater.inflate(R.layout.rdfri, container, false);
}
}
I would like to try and get a imageButton in the fragment. I figured image bottons work with this code:
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.rdmon);
ImageButton rainbowbook = (ImageButton) findViewById(R.id.imageButton1);
rainbowbook.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent myIntent = Intent(rdmon.this, RainbowBookActivity.class);
rdmon.this.startActivity(myIntent);
}
});
}
So how would I go about getting the button code in the fragment code?
Thanks.
Put the button code in the overridden onActivityCreated method in your fragment class.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
ImageButton rainbowbook = (ImageButton) findViewById(R.id.imageButton1);
rainbowbook.setOnClickListener(new View.OnClickListener() {
public void onClick(View view) {
Intent myIntent = Intent(rdmon.this, RainbowBookActivity.class);
rdmon.this.startActivity(myIntent);
}
});
}
This assumes of course that your imagebutton is contained in your fragment layout.
i am encapsulating stuff into a fragment at the moment and run into a problem that is hard to google.
Inside my fragment are some buttons with onClick attributes but they are called on the Activity rather the fragment from the android system - this makes encapsulating a bit clumsy. Is there a way to have the reflection stuff from onClick to call on the fragment? The only solution to this I see at the moment is not to use onClick in the xml and set click-listeners inside the fragment via code.
I spoke to some googlers # #adl2011 - they recognize the problem and perhaps there will be a fix of that in the future. Until then - one should use .setOnClick in the Fragment.
The problem is that when layout's are inflated it is still the hosting Activity that is receiving the button clicks, not the individual Fragments.
I prefer using the following solution for handling onClick events. This works for Activity and Fragments as well.
public class StartFragment extends Fragment implements OnClickListener{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_start, container, false);
Button b = (Button) v.findViewById(R.id.StartButton);
b.setOnClickListener(this);
return v;
}
#Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.StartButton:
...
break;
}
}
}
Then problem is gone.
It works for me
Add import:
import android.view.View.OnClickListener;
Fragment.java
...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = (ViewGroup) inflater.inflate(R.layout.fragment, container, false);
Button mButton = (Button) rootView.findViewById(R.id.button);
mButton.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
}
});
return rootView;
}
ISSUE:
1.In XML onClick attribute will call activity's public method.
2.Fragment's public method not called.
3.Fragment reusability.
SOLUTION:
In Fragment's layout add this to the View.
android:onClick="onFragmentViewClick"
In each activity the fragment may belong..
public void onFragmentViewClick(View v) {
Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.container);
if (fragment != null && fragment.isVisible()) {
if (fragment instanceof SomeFragment) {
((SomeFragment) fragment).onViewClicked(v);
}
}
}
In the Fragment include this method..
public void onViewClicked(View v) {
switch (v.getId()) {
case R.id.view_id:
Log.e("onViewClicked", "yehhh!!");
break;
}
}
You could have the listener that is being called in the activity forward the call onto a listener in the fragment. You should have a reference to the fragment inside of the FragmentActivity to pass the call on. You will have to cast to call the method or have your fragment implement an interface you define. I know that isn't the best solution but it will work. You could also use the tag of a button to specify the method name to call if you wanted. Hope this helps a bit.
Try this...
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
View rootView = inflater.inflate(R.layout.activity_ipadditional_users, container, false);
FloatingActionButton button7 = (FloatingActionButton) rootView.findViewById(R.id.fab_ip_additional_user);
button7.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
Intent intent = new Intent(getActivity(), IPAdditionalUsersEdit.class);
startActivity(intent);
}
});
return rootView;