Here is my main activity. I followed this guide about Fragments correctly. When I click "Back" button, my application is closed instead of returning to the MainScreenFragment. Why is this happening and why addToBackStack() doesn't work?
public class MainScreenActivity extends ActionBarActivity implements MainScreenFragment.OnFrameChoiced {
private MainScreenFragment mainScreenFragment;
private AddWordsFragment addWordsFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_screen);
mainScreenFragment = new MainScreenFragment();
addWordsFragment = new AddWordsFragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.container, mainScreenFragment).addToBackStack(null).commit();
}
#Override
public void choiceFrame(int id) {
switch (id) {
case R.id.add_new_words_frame:
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.container, addWordsFragment).addToBackStack(null).commit();
fm.executePendingTransactions();
break;
}
}
P.S. I tried to use a solution from this topic, but It still doesn't work.
did you try overriding the back like below:
#overide
public void onBackPressed(){
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
}
(I know you have picked up your desirable answer, but I have found a little more against this problem)
Though as Android official site has documented:
By calling addToBackStack(), the replace transaction is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Backbutton.
But as a matter of fact, this is in a precondition that you are using the standard android activity, specifically, the android.app.Activity. Because this methods in android.app.Activity will work when Backbutton is pressed:
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
if (!mFragments.getFragmentManager().popBackStackImmediate()) {
finishAfterTransition();
}
}
But, if you are extending your xxxxActivity from someone else, for example, the AppCompatActivity, FragmentActivity, ActionBarActivity, it will be another story, because in FragmentActivity, onBackPressed() method is totally overrode:
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
Note that mFragments.getFragmentManager() is replaced by mFragments.getSupportFragmentManager(), so in cases like this, you should begin your FragmentTransaction using getSupportFragmentManager() of the Activity. and as a consequence of that, you don't have to override onBackPressed method in your Activity.
BTW, ActionBarActivity extends AppCompatActivity extends FragmentActivity,they all come from the support library, you know what I mean, remember to use getSupportFragmentManager() instead of getFragmentManager() when using support library in order to get the compatible behavior.
Related
I have a Main Activity with a bunch of Fragments connected to it.
One of the fragments has to be able to recieve data from MainActivity after it has been loaded in the FragmentTransaction and committed.
What is the best way to call a method in that specific fragment?
Do I have to implement a Interface and include it in the MainActivty just for this one fragment? Is there a better way? Can someone point me in the correct direction?
What I have tried now : (Failing at settings ContactsInterface in the MainActivity)
ContactsInterface
public interface ContactsInterface {
void notifyDenied();
void notifyGranted();
}
Fragment
Class.... implements ContactsInterface...
#Override
public void notifyDenied() {
Log.d("DENIED", "DENIED CALLBACK");
}
#Override
public void notifyGranted() {
Log.d("GRANTED", "GRANTED CALLBACK");
}
Main Activity
try {
contactsInterface = (ContactsInterface) this.getApplicationContext();
} catch (ClassCastException e) {
throw new ClassCastException(this.toString()
+ " Needs to implement the methods");
}
Last example throws an ClassCastException.
You need to cast the fragment itself not the Application Context.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
ContactsInterface contactsInterface = (ContactsInterface) fragment;
//contactsInterface.notifyGranted();
//contactsInterface.notifyDenied();
You can get all the active fragments in the FragmentManager and filter for the ones that have the required interface:
supportFragmentManager.fragments
.map { it as? ContactsInterface }
.filterNotNull()
.forEach {
it.notifyDenied()
}
or if you need to use Java:
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(fragment : fragments) {
if(fragment instanceof ContactsInterface) {
((ContactsInterface)fragment).notifyDenied();
}
}
You may want to also fail (hard or soft) if no fragments are found.
The advantage of doing this is that you don't care how the fragment was started (manually or by layout) and also don't have to care about reconnecting when the activity restarts for example.
This library may be useful to you.
You should do something like this in your fragment:
#Override
public void onCreateView() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onDestroyView() {
super.onStop();
EventBus.getDefault().unregister(this);
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
MessageEvent is just an example, you can use whatever structure you want.
in the Activity you post messages like this:
EventBus.getDefault().post(new MessageEvent());
Check the documentation of the library to have a better idea and use a better approach to your case.
you can use onCreate() and onDestroy() instead. That depends on your logic and you you are updating the view of the fragment when you receive new messages from the Activity.
Happy coding :).
I read Android documentation on how to add a Fragment to an activty. It says that to add a Fragment to an Activty I should write this code inside the Activity class:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
but this is for android.app.Fragment class.
For android.support.v4.Fragment, documentation say that instead of getFragmentManager() I should call getSupportFragmentManager() and that Activity must extends FragmentActivty.
So I did this change, and now this is my activity code:
public class ExampleActivity extends AppCompactActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
AppCompactActivity is a subclass of FragmentActivity so I respected the constraints.
The problem is that if I run my application I get this error:
java.lang.RuntimeException:
com.myapplication.ExampleActivity#13388c6 must implement OnFragmentInteractionListener
I typed OnFragmentInteractionListener on Android doc web search and this is what I get:
Immediately below there is the link
https://developer.android.com/training/basics/fragments/communicating.html
that shows me a guide to communication between fragments. It speaks about ListFragment and I don't care it.
I'm very very confuse because every time I read documentation there are always things that force me to make internet search to find workaround or fix to problems.
Is there a persone that can explain me first how to fix this problem.
From the tutorial, you can read the following:
In order to receive event callbacks from the fragment, the activity
that hosts it must implement the interface defined in the fragment
class.
So, implement it in your Activity with something like this in your fragment:
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
}
Why did you need it? Because you've forced the host activity to implement it with something like this:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception
try {
mCallback = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
So i have this code in android (interface method implementation in the activity) The app runs perfectly well.
public void Clicks(int clickCounter) {
FragmentManager fragmentManager = getFragmentManager();
AnotherFragment another_fragment = (AnotherFragment) fragmentManager.findFragmentById(R.id.another_fragment);
another_fragment.showClicks(clickCounter);
}
Now, when i try to declare fragmentManager and another_fragment as class variables like so:
public class MainActivity extends AppCompatActivity implements FragmentInterface{
FragmentManager fragmentManager;
AnotherFragment anotherFragment;
fragmentManager = getFragmentManager();
anotherFragment = (AnotherFragment) fragmentManager.findFragmentById(R.id.another_fragment);
...
}
it causes the app to crash. Why is it acting this way?
fragmentManager = getFragmentManager();
anotherFragment = (AnotherFragment) fragmentManager.findFragmentById(R.id.another_fragment);
is not in any method block. You are trying to fetch those values before Activity is ready. Move this inside a method which is called after loading of AnotherFragment.
I bet you get a NullPointerException, right?
That's because you run your code before your Activity "is ready".
Your Activtiy is ready in onCreate(), for example. So put our code there.
public class MainActivity extends AppCompatActivity implements FragmentInterface {
FragmentManager fragmentManager;
AnotherFragment anotherFragment;
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.myLayout);
fragmentManager = getFragmentManager();
anotherFragment = (AnotherFragment) fragmentManager.findFragmentById(R.id.another_fragment);
}
}
You can find out more about the Activity Lifecycle here.
When my app switches to background,the system memory is low, then android will destroy my activity.At that time,I want to remove the fragments attached to activity,so that when activity switches to foreground,I can avoid any abnormal behavior of that activity.I do it like this:
#Override
public void onSaveInstanceState(Bundle outState) {
FragmentManager fm = getSupportFragmentManager();
AbstractBaseViewFragment previous = (AbstractBaseViewFragment) fm.findFragmentByTag(FRAGMENT_TAG);
if (previous != null) {
fm.beginTransaction().remove(previous).commitAllowingStateLoss();
fm.executePendingTransactions();
}
super.onSaveInstanceState(outState);
}
so I want to ask:Are there any other good ideas to finish this target?Will my method cause any crashes?
You can try this: ft.detach(previous);
if (previous != null) {
FragmentTransaction ft = fm.beginTransaction();
ft.detach(previous);
ft.remove(previous);
ft.commitAllowingStateLoss();
... // the rest of your code
}
Working with Robolectric , I'm very new to android. I made a first test class using Activity. It worked nicely.
Now I want make a test for fragment.
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment(fragment);
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment(LoginFragment fragment) {
FragmentManager fragmentManager = new FragmentActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText)fragment.getActivity().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is my first test class for Fragment class. It throws
"java.lang.IllegalStateException: Activity has been destroyed" in startFragment#fragmentTransaction.commit().
Anyone knows how to fix this ?
You can find whole source from https://github.com/msbaek/frame-test
Thanks in advance !!
In my case, specifically, my problem was when creating the activity.
I was using
activity = Robolectric.buildActivity(MyActivity.class).get();
And it should be
activity = Robolectric.buildActivity(MyActivity.class).create().get();
Hope it helps someone :D
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment();
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment() {
FragmentActivity activity = new FragmentActivity();
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText) fragment.getView().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is working version. Following 3 lines are important(it's from robolectric source - DialogFragmentTest).
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
The fragments are supposed to be displayed from an Activity. The flow should be:
allocate a new fragment object in a FragmentActivity class
get the fragment manager to add the newly allocated fragment
In your case you do not have a connection to a real activity. You allocate a FragmentActivity with new FragmentActivity() and try to get the support manager. While this compiles there is no "real" activity able to manage your fragment. Fragments can be added on activities already displayed and here it's not the case.
I recommend reading this page as it explains these things very well: http://developer.android.com/guide/components/fragments.html
That happened for me when I used fragmentTransaction.commitAllowingStateLoss(); from sub Fragment whose parent fragment had setRetainInstance(true); I had activity as property what lead to leaking activity on rotation.